From 53a8c86644460301c873d2a7a85dfae735737d66 Mon Sep 17 00:00:00 2001 From: Peter Trost Date: Wed, 28 May 2025 15:28:30 +0200 Subject: [PATCH 1/9] feat: allow analyzer 7.0.0 --- pkgs/intl_translation/pubspec.yaml | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/intl_translation/pubspec.yaml b/pkgs/intl_translation/pubspec.yaml index dfa3bc53..34a779ee 100644 --- a/pkgs/intl_translation/pubspec.yaml +++ b/pkgs/intl_translation/pubspec.yaml @@ -15,7 +15,7 @@ environment: sdk: ^3.0.0 dependencies: - analyzer: ^6.3.0 + analyzer: ">=6.3.0 <8.0.0" args: ^2.0.0 dart_style: ^2.0.0 intl: '>=0.19.0 <0.21.0' From ec4d8d2f0ecefa7c5ca75d0109e8a5b84e667c03 Mon Sep 17 00:00:00 2001 From: Peter Trost Date: Wed, 28 May 2025 15:33:45 +0200 Subject: [PATCH 2/9] chore: bump version and update changelog --- pkgs/intl_translation/CHANGELOG.md | 3 +++ pkgs/intl_translation/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 1 deletion(-) diff --git a/pkgs/intl_translation/CHANGELOG.md b/pkgs/intl_translation/CHANGELOG.md index a20ad7d2..a9996fab 100644 --- a/pkgs/intl_translation/CHANGELOG.md +++ b/pkgs/intl_translation/CHANGELOG.md @@ -1,3 +1,6 @@ +## 0.20.2-wip + * Allow analyzer 7.0.0 + ## 0.20.1 * Add topics to `pubspec.yaml` * Update to `dart_style `2.3.7`. `bin/make_examples_const.dart` and diff --git a/pkgs/intl_translation/pubspec.yaml b/pkgs/intl_translation/pubspec.yaml index 34a779ee..a9dca7c2 100644 --- a/pkgs/intl_translation/pubspec.yaml +++ b/pkgs/intl_translation/pubspec.yaml @@ -1,5 +1,5 @@ name: intl_translation -version: 0.20.1 +version: 0.20.2-wip description: >- Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and From 5e7fe068ead91aa4c4e9f77aed5afe656514daab Mon Sep 17 00:00:00 2001 From: Peter Trost Date: Sun, 1 Jun 2025 19:46:20 +0200 Subject: [PATCH 3/9] chore: Update dart_style to ^3.0.0 --- pkgs/intl_translation/CHANGELOG.md | 3 +- pkgs/intl_translation/pubspec.yaml | 2 +- .../code_map_messages_all.dart | 78 ++++++++++--------- .../code_map_messages_all_locales.dart | 11 +-- .../code_map_messages_fr.dart | 14 ++-- .../test/two_components/app_messages_all.dart | 4 +- .../app_messages_all_locales.dart | 11 +-- .../test/two_components/app_messages_fr.dart | 13 ++-- .../component_messages_all.dart | 4 +- .../component_messages_all_locales.dart | 11 +-- .../component_messages_fr_xyz123.dart | 15 ++-- 11 files changed, 90 insertions(+), 76 deletions(-) diff --git a/pkgs/intl_translation/CHANGELOG.md b/pkgs/intl_translation/CHANGELOG.md index a9996fab..f53c6352 100644 --- a/pkgs/intl_translation/CHANGELOG.md +++ b/pkgs/intl_translation/CHANGELOG.md @@ -1,9 +1,10 @@ ## 0.20.2-wip * Allow analyzer 7.0.0 + * Update `dart_style` to `^3.0.0` ## 0.20.1 * Add topics to `pubspec.yaml` - * Update to `dart_style `2.3.7`. `bin/make_examples_const.dart` and + * Update to `dart_style` `2.3.7`. `bin/make_examples_const.dart` and `rewrite_intl_messages.dart` will now look for a surrounding `.dart_tool/package_config.json` file to infer the language version of the files updated by the script. diff --git a/pkgs/intl_translation/pubspec.yaml b/pkgs/intl_translation/pubspec.yaml index a9dca7c2..7e98e714 100644 --- a/pkgs/intl_translation/pubspec.yaml +++ b/pkgs/intl_translation/pubspec.yaml @@ -17,7 +17,7 @@ environment: dependencies: analyzer: ">=6.3.0 <8.0.0" args: ^2.0.0 - dart_style: ^2.0.0 + dart_style: ^3.0.0 intl: '>=0.19.0 <0.21.0' package_config: ^2.1.0 path: ^1.0.0 diff --git a/pkgs/intl_translation/test/generate_localized/code_map_messages_all.dart b/pkgs/intl_translation/test/generate_localized/code_map_messages_all.dart index a4e4df4b..a82c78c7 100644 --- a/pkgs/intl_translation/test/generate_localized/code_map_messages_all.dart +++ b/pkgs/intl_translation/test/generate_localized/code_map_messages_all.dart @@ -1,9 +1,12 @@ // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart // This is a library that looks up messages for specific locales by // delegating to the appropriate library. +// @dart=2.12 import 'package:intl/intl.dart'; -export 'code_map_messages_all_locales.dart' show initializeMessages; +export 'code_map_messages_all_locales.dart' + show initializeMessages; + /// Turn the JSON template into a string. /// @@ -26,39 +29,44 @@ String? evaluateJsonTemplate(dynamic input, List args) { var template = input as List; var messageName = template.first; if (messageName == 'Intl.plural') { - var howMany = args[template[1] as int] as num; - return evaluateJsonTemplate( - Intl.pluralLogic(howMany, - zero: template[2], - one: template[3], - two: template[4], - few: template[5], - many: template[6], - other: template[7]), - args); - } - if (messageName == 'Intl.gender') { - var gender = args[template[1] as int] as String; - return evaluateJsonTemplate( - Intl.genderLogic(gender, - female: template[2], male: template[3], other: template[4]), - args); - } - if (messageName == 'Intl.select') { - var select = args[template[1] as int] as Object; - var choices = template[2] as Map; - return evaluateJsonTemplate(Intl.selectLogic(select, choices), args); - } + var howMany = args[template[1] as int] as num; + return evaluateJsonTemplate( + Intl.pluralLogic( + howMany, + zero: template[2], + one: template[3], + two: template[4], + few: template[5], + many: template[6], + other: template[7]), + args); + } + if (messageName == 'Intl.gender') { + var gender = args[template[1] as int] as String; + return evaluateJsonTemplate( + Intl.genderLogic( + gender, + female: template[2], + male: template[3], + other: template[4]), + args); + } + if (messageName == 'Intl.select') { + var select = args[template[1] as int] as Object; + var choices = template[2] as Map; + return evaluateJsonTemplate(Intl.selectLogic(select, choices), args); + } - // If we get this far, then we are a basic interpolation, just strings and - // ints. - var output = StringBuffer(); - for (var entry in template) { - if (entry is int) { - output.write('${args[entry]}'); - } else { - output.write('$entry'); - } + // If we get this far, then we are a basic interpolation, just strings and + // ints. + var output = StringBuffer(); + for (var entry in template) { + if (entry is int) { + output.write('${args[entry]}'); + } else { + output.write('$entry'); + } + } + return output.toString(); } - return output.toString(); -} + diff --git a/pkgs/intl_translation/test/generate_localized/code_map_messages_all_locales.dart b/pkgs/intl_translation/test/generate_localized/code_map_messages_all_locales.dart index 2cbc47ed..f129b600 100644 --- a/pkgs/intl_translation/test/generate_localized/code_map_messages_all_locales.dart +++ b/pkgs/intl_translation/test/generate_localized/code_map_messages_all_locales.dart @@ -1,7 +1,7 @@ // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart // This is a library that looks up messages for specific locales by // delegating to the appropriate library. - +// @dart=2.12 // Ignore issues from commonly used lints in this file. // ignore_for_file:implementation_imports, file_names // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering @@ -32,8 +32,9 @@ MessageLookupByLibrary? _findExact(String localeName) { /// User programs should call this before using [localeName] for messages. Future initializeMessages(String? localeName) async { var availableLocale = Intl.verifiedLocale( - localeName, (locale) => _deferredLibraries[locale] != null, - onFailure: (_) => null); + localeName, + (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); if (availableLocale == null) { return Future.value(false); } @@ -53,8 +54,8 @@ bool _messagesExistFor(String locale) { } MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { - var actualLocale = - Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); + var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, + onFailure: (_) => null); if (actualLocale == null) return null; return _findExact(actualLocale); } diff --git a/pkgs/intl_translation/test/generate_localized/code_map_messages_fr.dart b/pkgs/intl_translation/test/generate_localized/code_map_messages_fr.dart index 1dc09d21..b8bd04cc 100644 --- a/pkgs/intl_translation/test/generate_localized/code_map_messages_fr.dart +++ b/pkgs/intl_translation/test/generate_localized/code_map_messages_fr.dart @@ -2,7 +2,7 @@ // This is a library that provides messages for a fr locale. All the // messages from the main program should be duplicated here with the same // function name. - +// @dart=2.12 // Ignore issues from commonly used lints in this file. // ignore_for_file:unnecessary_brace_in_string_interps // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering @@ -19,18 +19,18 @@ import 'dart:collection'; final messages = MessageLookup(); -typedef String? MessageIfAbsent(String? messageStr, List? args); +typedef String? MessageIfAbsent( + String? messageStr, List? args); class MessageLookup extends MessageLookupByLibrary { @override String get localeName => 'fr'; + String? evaluateMessage(dynamic translation, List args) { return evaluateJsonTemplate(translation, args); } - Map get messages => _constMessages; - static const _constMessages = { - "Hello from application": "Bonjour de l'application" - }; -} + static const _constMessages = {"Hello from application":["Bonjour de l'application"]}; + +} \ No newline at end of file diff --git a/pkgs/intl_translation/test/two_components/app_messages_all.dart b/pkgs/intl_translation/test/two_components/app_messages_all.dart index ca710f22..a6905f88 100644 --- a/pkgs/intl_translation/test/two_components/app_messages_all.dart +++ b/pkgs/intl_translation/test/two_components/app_messages_all.dart @@ -1,5 +1,7 @@ // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart // This is a library that looks up messages for specific locales by // delegating to the appropriate library. +// @dart=2.12 +export 'app_messages_all_locales.dart' + show initializeMessages; -export 'app_messages_all_locales.dart' show initializeMessages; diff --git a/pkgs/intl_translation/test/two_components/app_messages_all_locales.dart b/pkgs/intl_translation/test/two_components/app_messages_all_locales.dart index 793c9d3d..8d259c43 100644 --- a/pkgs/intl_translation/test/two_components/app_messages_all_locales.dart +++ b/pkgs/intl_translation/test/two_components/app_messages_all_locales.dart @@ -1,7 +1,7 @@ // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart // This is a library that looks up messages for specific locales by // delegating to the appropriate library. - +// @dart=2.12 // Ignore issues from commonly used lints in this file. // ignore_for_file:implementation_imports, file_names // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering @@ -32,8 +32,9 @@ MessageLookupByLibrary? _findExact(String localeName) { /// User programs should call this before using [localeName] for messages. Future initializeMessages(String? localeName) async { var availableLocale = Intl.verifiedLocale( - localeName, (locale) => _deferredLibraries[locale] != null, - onFailure: (_) => null); + localeName, + (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); if (availableLocale == null) { return Future.value(false); } @@ -53,8 +54,8 @@ bool _messagesExistFor(String locale) { } MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { - var actualLocale = - Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); + var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, + onFailure: (_) => null); if (actualLocale == null) return null; return _findExact(actualLocale); } diff --git a/pkgs/intl_translation/test/two_components/app_messages_fr.dart b/pkgs/intl_translation/test/two_components/app_messages_fr.dart index d4908b4c..9d17ab21 100644 --- a/pkgs/intl_translation/test/two_components/app_messages_fr.dart +++ b/pkgs/intl_translation/test/two_components/app_messages_fr.dart @@ -2,7 +2,7 @@ // This is a library that provides messages for a fr locale. All the // messages from the main program should be duplicated here with the same // function name. - +// @dart=2.12 // Ignore issues from commonly used lints in this file. // ignore_for_file:unnecessary_brace_in_string_interps // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering @@ -14,18 +14,17 @@ import 'package:intl/message_lookup_by_library.dart'; final messages = MessageLookup(); -typedef String? MessageIfAbsent(String? messageStr, List? args); +typedef String? MessageIfAbsent( + String? messageStr, List? args); class MessageLookup extends MessageLookupByLibrary { @override String get localeName => 'fr'; @override - final Map messages = - _notInlinedMessages(_notInlinedMessages); + final Map messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - 'Hello from application': - MessageLookupByLibrary.simpleMessage('Bonjour de l\'application') - }; + 'Hello from application': MessageLookupByLibrary.simpleMessage('Bonjour de l\'application') + }; } diff --git a/pkgs/intl_translation/test/two_components/component_messages_all.dart b/pkgs/intl_translation/test/two_components/component_messages_all.dart index 77101afe..505188af 100644 --- a/pkgs/intl_translation/test/two_components/component_messages_all.dart +++ b/pkgs/intl_translation/test/two_components/component_messages_all.dart @@ -1,5 +1,7 @@ // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart // This is a library that looks up messages for specific locales by // delegating to the appropriate library. +// @dart=2.12 +export 'component_messages_all_locales.dart' + show initializeMessages; -export 'component_messages_all_locales.dart' show initializeMessages; diff --git a/pkgs/intl_translation/test/two_components/component_messages_all_locales.dart b/pkgs/intl_translation/test/two_components/component_messages_all_locales.dart index 5b395793..361cee74 100644 --- a/pkgs/intl_translation/test/two_components/component_messages_all_locales.dart +++ b/pkgs/intl_translation/test/two_components/component_messages_all_locales.dart @@ -1,7 +1,7 @@ // DO NOT EDIT. This is code generated via package:intl/generate_localized.dart // This is a library that looks up messages for specific locales by // delegating to the appropriate library. - +// @dart=2.12 // Ignore issues from commonly used lints in this file. // ignore_for_file:implementation_imports, file_names // ignore_for_file:unnecessary_brace_in_string_interps, directives_ordering @@ -32,8 +32,9 @@ MessageLookupByLibrary? _findExact(String localeName) { /// User programs should call this before using [localeName] for messages. Future initializeMessages(String? localeName) async { var availableLocale = Intl.verifiedLocale( - localeName, (locale) => _deferredLibraries[locale] != null, - onFailure: (_) => null); + localeName, + (locale) => _deferredLibraries[locale] != null, + onFailure: (_) => null); if (availableLocale == null) { return Future.value(false); } @@ -53,8 +54,8 @@ bool _messagesExistFor(String locale) { } MessageLookupByLibrary? _findGeneratedMessagesFor(String locale) { - var actualLocale = - Intl.verifiedLocale(locale, _messagesExistFor, onFailure: (_) => null); + var actualLocale = Intl.verifiedLocale(locale, _messagesExistFor, + onFailure: (_) => null); if (actualLocale == null) return null; return _findExact(actualLocale); } diff --git a/pkgs/intl_translation/test/two_components/component_messages_fr_xyz123.dart b/pkgs/intl_translation/test/two_components/component_messages_fr_xyz123.dart index 49c18609..58d89dee 100644 --- a/pkgs/intl_translation/test/two_components/component_messages_fr_xyz123.dart +++ b/pkgs/intl_translation/test/two_components/component_messages_fr_xyz123.dart @@ -2,7 +2,7 @@ // This is a library that provides messages for a fr_xyz123 locale. All the // messages from the main program should be duplicated here with the same // function name. - +// @dart=2.12 // Ignore issues from commonly used lints in this file. // ignore_for_file:unnecessary_brace_in_string_interps // ignore_for_file:prefer_single_quotes,comment_references, directives_ordering @@ -14,19 +14,18 @@ import 'package:intl/message_lookup_by_library.dart'; final messages = MessageLookup(); -typedef String? MessageIfAbsent(String? messageStr, List? args); +typedef String? MessageIfAbsent( + String? messageStr, List? args); class MessageLookup extends MessageLookupByLibrary { @override String get localeName => 'fr_xyz123'; @override - final Map messages = - _notInlinedMessages(_notInlinedMessages); + final Map messages = _notInlinedMessages(_notInlinedMessages); static Map _notInlinedMessages(_) => { - 'Hello from component': - MessageLookupByLibrary.simpleMessage('Bonjour du composant'), - '_message2': MessageLookupByLibrary.simpleMessage('Locale explicite') - }; + 'Hello from component': MessageLookupByLibrary.simpleMessage('Bonjour du composant'), + '_message2': MessageLookupByLibrary.simpleMessage('Locale explicite') + }; } From 2e688099fd7b42e48218b76eceacc6afc247c803 Mon Sep 17 00:00:00 2001 From: Peter Trost Date: Mon, 2 Jun 2025 08:43:31 +0200 Subject: [PATCH 4/9] chore: update changelog --- pkgs/intl_translation/CHANGELOG.md | 6 +++--- pkgs/intl_translation/pubspec.yaml | 2 +- 2 files changed, 4 insertions(+), 4 deletions(-) diff --git a/pkgs/intl_translation/CHANGELOG.md b/pkgs/intl_translation/CHANGELOG.md index f53c6352..8d6d71d5 100644 --- a/pkgs/intl_translation/CHANGELOG.md +++ b/pkgs/intl_translation/CHANGELOG.md @@ -1,6 +1,6 @@ -## 0.20.2-wip - * Allow analyzer 7.0.0 - * Update `dart_style` to `^3.0.0` +## 0.21.0-wip + * BREAKING CHANGE: Update `dart_style` to `^3.0.0` + * Allow analyzer `>=6.3.0 <8.0.0` ## 0.20.1 * Add topics to `pubspec.yaml` diff --git a/pkgs/intl_translation/pubspec.yaml b/pkgs/intl_translation/pubspec.yaml index 7e98e714..d457091b 100644 --- a/pkgs/intl_translation/pubspec.yaml +++ b/pkgs/intl_translation/pubspec.yaml @@ -1,5 +1,5 @@ name: intl_translation -version: 0.20.2-wip +version: 0.21.0-wip description: >- Contains code to deal with internationalized/localized messages, date and number formatting and parsing, bi-directional text, and From 350591e22e4ed79bd5658abfb603b9c6fc7239de Mon Sep 17 00:00:00 2001 From: Moritz Date: Tue, 3 Jun 2025 14:25:59 +0200 Subject: [PATCH 5/9] Remove analyzer dep --- .../lib/src/messages/main_message.dart | 2 +- .../lib/src/messages/message.dart | 61 ++++++++++--------- 2 files changed, 33 insertions(+), 30 deletions(-) diff --git a/pkgs/intl_translation/lib/src/messages/main_message.dart b/pkgs/intl_translation/lib/src/messages/main_message.dart index 9943fde1..26ff54f1 100644 --- a/pkgs/intl_translation/lib/src/messages/main_message.dart +++ b/pkgs/intl_translation/lib/src/messages/main_message.dart @@ -33,7 +33,7 @@ class MainMessage extends ComplexMessage { /// Verify that this looks like a correct Intl.message invocation. static void checkValidity( MethodInvocation node, - List arguments, + List arguments, String? outerName, List outerArgs, { bool nameAndArgsGenerated = false, diff --git a/pkgs/intl_translation/lib/src/messages/message.dart b/pkgs/intl_translation/lib/src/messages/message.dart index e66bdf34..ee72d6af 100644 --- a/pkgs/intl_translation/lib/src/messages/message.dart +++ b/pkgs/intl_translation/lib/src/messages/message.dart @@ -35,8 +35,6 @@ library intl_message; import 'dart:convert'; import 'package:analyzer/dart/ast/ast.dart'; -// ignore: implementation_imports -import 'package:analyzer/src/dart/ast/constant_evaluator.dart'; import 'complex_message.dart'; import 'composite_message.dart'; @@ -75,26 +73,6 @@ abstract class Message { /// The name of the top-level [MainMessage]. String get name => parent == null ? '' : parent!.name; - static final _evaluator = ConstantEvaluator(); - - static String? _evaluateAsString(expression) { - var result = expression.accept(_evaluator); - if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! String) { - return null; - } else { - return result; - } - } - - static Map? _evaluateAsMap(Expression expression) { - var result = expression.accept(_evaluator); - if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! Map) { - return null; - } else { - return result; - } - } - /// Verify that the args argument matches the method parameters and /// isn't, e.g. passing string names instead of the argument values. static bool checkArgs(NamedExpression? args, List parameterNames) { @@ -141,7 +119,7 @@ abstract class Message { /// for messages with parameters. static void checkValidity( MethodInvocation node, - List arguments, + List arguments, String? outerName, List outerArgs, { bool nameAndArgsGenerated = false, @@ -165,7 +143,7 @@ abstract class Message { ' e.g. args: $parameterNames'); } - var nameNamedExps = arguments + final nameNamedExps = arguments .whereType() .where((arg) => arg.name.label.name == 'name') .map((e) => e.expression); @@ -177,7 +155,7 @@ abstract class Message { if (nameNamedExps.isEmpty) { if (!hasParameters) { // No name supplied, no parameters. Use the message as the name. - var name = _evaluateAsString(arguments[0]); + var name = evaluateConstString(arguments[0]); messageName = name; outerName = name; } else { @@ -195,7 +173,7 @@ abstract class Message { } } else { // Name argument is supplied, use it. - var name = _evaluateAsString(nameNamedExps.first); + var name = evaluateConstString(nameNamedExps.first); messageName = name; givenName = name; } @@ -227,7 +205,7 @@ abstract class Message { .map((each) => each.expression) .toList(); for (var arg in values) { - if (_evaluateAsString(arg) == null) { + if (evaluateConstString(arg) == null) { throw MessageExtractionException( 'Intl.message arguments must be string literals: $arg'); } @@ -245,8 +223,7 @@ abstract class Message { if (examples.isNotEmpty) { var example = examples.first; if (example is SetOrMapLiteral) { - var map = _evaluateAsMap(example); - if (map == null) { + if (!_isConstMap(example)) { throw MessageExtractionException( 'Examples must be a const Map literal.'); } else if (example.constKeyword == null) { @@ -374,3 +351,29 @@ abstract class Message { /// suitable for a wide variety of translation file formats. String expanded([String Function(Message, Object) transform]); } + +String? evaluateConstString(Expression expression) => switch (expression) { + SimpleStringLiteral() => expression.value, + AdjacentStrings() => _checkChildren(expression.strings)?.join(), + _ => null, + }; + +Iterable? _checkChildren(Iterable strings) { + final constExpressions = strings.map( + (string) => evaluateConstString(string), + ); + return constExpressions.any( + (string) => string == null, + ) + ? null + : constExpressions.whereType(); +} + +bool _isConstMap(SetOrMapLiteral map) { + if (map.elements.any( + (element) => element is! MapLiteralEntry, + )) { + return false; + } + return map.isConst; +} From 3dfd2995f7b13658cf54ec44502317de5e165d9b Mon Sep 17 00:00:00 2001 From: Peter Trost Date: Wed, 4 Jun 2025 10:06:30 +0200 Subject: [PATCH 6/9] refactor: remove unused generic type --- pkgs/intl_translation/lib/src/messages/message.dart | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/pkgs/intl_translation/lib/src/messages/message.dart b/pkgs/intl_translation/lib/src/messages/message.dart index ee72d6af..c9c5313d 100644 --- a/pkgs/intl_translation/lib/src/messages/message.dart +++ b/pkgs/intl_translation/lib/src/messages/message.dart @@ -358,7 +358,7 @@ String? evaluateConstString(Expression expression) => switch (expression) { _ => null, }; -Iterable? _checkChildren(Iterable strings) { +Iterable? _checkChildren(Iterable strings) { final constExpressions = strings.map( (string) => evaluateConstString(string), ); From 6f18964335460323ca51730ab9391e353061f767 Mon Sep 17 00:00:00 2001 From: Moritz Date: Wed, 4 Jun 2025 12:19:22 +0200 Subject: [PATCH 7/9] Fix --- .../lib/src/messages/constant_evaluator.dart | 73 +++++++++++++++++++ .../lib/src/messages/message.dart | 29 +------- .../lib/visitors/message_finding_visitor.dart | 9 +-- 3 files changed, 78 insertions(+), 33 deletions(-) create mode 100644 pkgs/intl_translation/lib/src/messages/constant_evaluator.dart diff --git a/pkgs/intl_translation/lib/src/messages/constant_evaluator.dart b/pkgs/intl_translation/lib/src/messages/constant_evaluator.dart new file mode 100644 index 00000000..11cbeaac --- /dev/null +++ b/pkgs/intl_translation/lib/src/messages/constant_evaluator.dart @@ -0,0 +1,73 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// 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. + +import 'package:analyzer/dart/ast/ast.dart'; + +/// Needed to wrap `null` as a valid constant value +class Constant { + final T value; + + Constant(this.value); + + static Constant? nullable(T? o) => + o == null ? null : Constant(o); +} + +Constant? evaluate(Expression expression) { + return switch (expression) { + AdjacentStrings() => Constant.nullable(evaluateConstString(expression)), + SimpleStringLiteral() => Constant(expression.value), + IntegerLiteral() => Constant(expression.value), + DoubleLiteral() => Constant(expression.value), + BooleanLiteral() => Constant(expression.value), + NullLiteral() => Constant(null), + SetOrMapLiteral() => evaluateConstStringMap(expression), + _ => null, + }; +} + +String? evaluateConstString(Expression expression) => switch (expression) { + SimpleStringLiteral() => expression.value, + AdjacentStrings() => _checkChildren(expression.strings)?.join(), + _ => null, + }; + +Iterable? _checkChildren(Iterable strings) { + final constExpressions = strings.map( + (string) => evaluateConstString(string), + ); + return constExpressions.any( + (string) => string == null, + ) + ? null + : constExpressions.whereType(); +} + +Constant? evaluateConstStringMap(SetOrMapLiteral map) { + if (!map.isConst) { + return null; + } + if (map.elements.any( + (element) => element is! MapLiteralEntry, + )) { + return null; + } + final evaluatedEntries = map.elements.whereType().map( + (literalEntry) => + (evaluate(literalEntry.key), evaluate(literalEntry.value)), + ); + if (evaluatedEntries + .any((element) => element.$1 == null || element.$2 == null)) { + return null; + } + var extractedValues = evaluatedEntries.map( + (literalEntry) => MapEntry(literalEntry.$1!.value, literalEntry.$2!.value), + ); + if (extractedValues.any((element) => element.key is! String)) { + return null; + } + return Constant(Map.fromEntries(extractedValues.map( + (e) => MapEntry(e.key as String, e.value), + ))); +} diff --git a/pkgs/intl_translation/lib/src/messages/message.dart b/pkgs/intl_translation/lib/src/messages/message.dart index c9c5313d..18ca1987 100644 --- a/pkgs/intl_translation/lib/src/messages/message.dart +++ b/pkgs/intl_translation/lib/src/messages/message.dart @@ -38,6 +38,7 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'complex_message.dart'; import 'composite_message.dart'; +import 'constant_evaluator.dart'; import 'literal_string_message.dart'; import 'message_extraction_exception.dart'; import 'variable_substitution_message.dart'; @@ -223,7 +224,7 @@ abstract class Message { if (examples.isNotEmpty) { var example = examples.first; if (example is SetOrMapLiteral) { - if (!_isConstMap(example)) { + if (evaluateConstStringMap(example) == null) { throw MessageExtractionException( 'Examples must be a const Map literal.'); } else if (example.constKeyword == null) { @@ -351,29 +352,3 @@ abstract class Message { /// suitable for a wide variety of translation file formats. String expanded([String Function(Message, Object) transform]); } - -String? evaluateConstString(Expression expression) => switch (expression) { - SimpleStringLiteral() => expression.value, - AdjacentStrings() => _checkChildren(expression.strings)?.join(), - _ => null, - }; - -Iterable? _checkChildren(Iterable strings) { - final constExpressions = strings.map( - (string) => evaluateConstString(string), - ); - return constExpressions.any( - (string) => string == null, - ) - ? null - : constExpressions.whereType(); -} - -bool _isConstMap(SetOrMapLiteral map) { - if (map.elements.any( - (element) => element is! MapLiteralEntry, - )) { - return false; - } - return map.isConst; -} diff --git a/pkgs/intl_translation/lib/visitors/message_finding_visitor.dart b/pkgs/intl_translation/lib/visitors/message_finding_visitor.dart index 1326126b..5280ca5f 100644 --- a/pkgs/intl_translation/lib/visitors/message_finding_visitor.dart +++ b/pkgs/intl_translation/lib/visitors/message_finding_visitor.dart @@ -4,10 +4,9 @@ import 'package:analyzer/dart/ast/ast.dart'; import 'package:analyzer/dart/ast/visitor.dart'; -// ignore: implementation_imports -import 'package:analyzer/src/dart/ast/constant_evaluator.dart'; import '../extract_messages.dart'; +import '../src/messages/constant_evaluator.dart'; import '../src/messages/main_message.dart'; import '../src/messages/message.dart'; import '../src/messages/message_extraction_exception.dart'; @@ -288,10 +287,8 @@ class MessageFindingVisitor extends GeneralizingAstVisitor { for (var namedExpr in arguments.whereType()) { var name = namedExpr.name.label.name; var exp = namedExpr.expression; - var basicValue = exp.accept(ConstantEvaluator()); - var value = basicValue == ConstantEvaluator.NOT_A_CONSTANT - ? exp.toString() - : basicValue; + var constant = evaluate(exp); + var value = constant == null ? exp.toString() : constant.value; setAttribute(message, name, value); } // We only rewrite messages with parameters, otherwise we use the literal From 4f444abdfb87ca6abf01e023538caf94ff47d59a Mon Sep 17 00:00:00 2001 From: Peter Trost Date: Wed, 4 Jun 2025 12:48:26 +0200 Subject: [PATCH 8/9] test: add tests for constant evaluator --- .../test/constant_evaluator_test.dart | 461 ++++++++++++++++++ 1 file changed, 461 insertions(+) create mode 100644 pkgs/intl_translation/test/constant_evaluator_test.dart diff --git a/pkgs/intl_translation/test/constant_evaluator_test.dart b/pkgs/intl_translation/test/constant_evaluator_test.dart new file mode 100644 index 00000000..3c53d4d8 --- /dev/null +++ b/pkgs/intl_translation/test/constant_evaluator_test.dart @@ -0,0 +1,461 @@ +// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file +// 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. + +import 'package:analyzer/dart/analysis/features.dart'; +import 'package:analyzer/dart/analysis/utilities.dart'; +import 'package:analyzer/dart/ast/ast.dart'; +import 'package:intl_translation/src/messages/constant_evaluator.dart'; +import 'package:test/test.dart'; + +void main() { + group('Constant class', () { + test('should wrap non-null values', () { + final constant = Constant('test'); + expect(constant.value, 'test'); + }); + + test('should wrap null values', () { + final constant = Constant(null); + expect(constant.value, isNull); + }); + + test('nullable should return null for null input', () { + final result = Constant.nullable(null); + expect(result, isNull); + }); + + test('nullable should wrap non-null values', () { + final result = Constant.nullable('test'); + expect(result, isNotNull); + expect(result!.value, 'test'); + }); + }); + + group('evaluate function', () { + test('should evaluate SimpleStringLiteral', () { + final expression = + _parseExpression('"hello world"') as SimpleStringLiteral; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, 'hello world'); + }); + + test('should evaluate AdjacentStrings', () { + final expression = + _parseExpression('"hello " "world"') as AdjacentStrings; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, 'hello world'); + }); + + test('should evaluate IntegerLiteral', () { + final expression = _parseExpression('42') as IntegerLiteral; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, 42); + }); + + test('should evaluate DoubleLiteral', () { + final expression = _parseExpression('3.14') as DoubleLiteral; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, 3.14); + }); + + test('should evaluate BooleanLiteral true', () { + final expression = _parseExpression('true') as BooleanLiteral; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, true); + }); + + test('should evaluate BooleanLiteral false', () { + final expression = _parseExpression('false') as BooleanLiteral; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, false); + }); + + test('should evaluate NullLiteral', () { + final expression = _parseExpression('null') as NullLiteral; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, isNull); + }); + + test('should evaluate const SetOrMapLiteral (map)', () { + final expression = _parseExpression('const {"key": "value", "num": 42}') + as SetOrMapLiteral; + final result = evaluate(expression); + + expect(result, isNotNull); + expect(result!.value, isA>()); + final map = result.value as Map; + expect(map['key'], 'value'); + expect(map['num'], 42); + }); + + test('should return null for unsupported expressions', () { + final expression = _parseExpression('someVariable') as SimpleIdentifier; + final result = evaluate(expression); + + expect(result, isNull); + }); + + test('should return null for function calls', () { + final expression = _parseExpression('someFunction()') as MethodInvocation; + final result = evaluate(expression); + + expect(result, isNull); + }); + }); + + group('evaluateConstString function', () { + test('should evaluate SimpleStringLiteral', () { + final expression = _parseExpression('"hello"') as SimpleStringLiteral; + final result = evaluateConstString(expression); + + expect(result, 'hello'); + }); + + test('should evaluate AdjacentStrings with multiple parts', () { + final expression = + _parseExpression('"hello " "beautiful " "world"') as AdjacentStrings; + final result = evaluateConstString(expression); + + expect(result, 'hello beautiful world'); + }); + + test('should evaluate AdjacentStrings with empty string', () { + final expression = + _parseExpression('"hello" "" "world"') as AdjacentStrings; + final result = evaluateConstString(expression); + + expect(result, 'helloworld'); + }); + + test('should return null for non-string expressions', () { + final expression = _parseExpression('42') as IntegerLiteral; + final result = evaluateConstString(expression); + + expect(result, isNull); + }); + + test('should return null for string interpolation', () { + final expression = + _parseExpression(r'"hello ${variable}"') as StringInterpolation; + final result = evaluateConstString(expression); + + expect(result, isNull); + }); + }); + + group('evaluateConstStringMap function', () { + test('should evaluate const map with string keys and mixed values', () { + final expression = _parseExpression( + 'const {"str": "value", "num": 42, "bool": true, "nil": null}') + as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNotNull); + final map = result!.value as Map; + expect(map['str'], 'value'); + expect(map['num'], 42); + expect(map['bool'], true); + expect(map['nil'], isNull); + }); + + test('should evaluate const empty map', () { + final expression = + _parseExpression('const {}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNotNull); + expect(result!.value, isA>()); + expect((result.value).isEmpty, isTrue); + }); + + test('should return null for non-const map', () { + final expression = + _parseExpression('{"key": "value"}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should return null for const set', () { + final expression = + _parseExpression('const {"item1", "item2"}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should return null for map with spread elements', () { + final expression = + _parseExpression('const {...other}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should return null for map with non-evaluable keys', () { + final expression = + _parseExpression('const {someVariable: "value"}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should return null for map with non-evaluable values', () { + final expression = + _parseExpression('const {"key": someVariable}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should return null for map with non-string keys', () { + final expression = + _parseExpression('const {42: "value"}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should handle nested const maps', () { + final expression = + _parseExpression('const {"outer": const {"inner": "value"}}') + as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNotNull); + final outerMap = result!.value as Map; + expect(outerMap['outer'], isA>()); + final innerMap = outerMap['outer'] as Map; + expect(innerMap['inner'], 'value'); + }); + }); + + group('Recursive and complex scenarios', () { + test('should handle nested AdjacentStrings in map values', () { + final expression = _parseExpression('const {"key": "hello " "world"}') + as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNotNull); + final map = result!.value as Map; + expect(map['key'], 'hello world'); + }); + + test('should handle nested AdjacentStrings in map keys', () { + final expression = _parseExpression('const {"hello " "world": "value"}') + as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNotNull); + final map = result!.value as Map; + expect(map['hello world'], 'value'); + }); + + test('should handle deeply nested const structures', () { + final expression = _parseExpression(''' + const { + "level1": const { + "level2": const { + "level3": "deep " "value" + } + } + } + ''') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNotNull); + final level1 = result!.value as Map; + final level2 = level1['level1'] as Map; + final level3 = level2['level2'] as Map; + expect(level3['level3'], 'deep value'); + }); + + test('should handle AdjacentStrings with many parts', () { + final expression = + _parseExpression('"a" "b" "c" "d" "e"') as AdjacentStrings; + final result = evaluateConstString(expression); + + expect(result, 'abcde'); + }); + + test('should handle complex map with all supported literal types', () { + final expression = _parseExpression(''' + const { + "string": "hello " "world", + "integer": 42, + "double": 3.14, + "boolean_true": true, + "boolean_false": false, + "null_value": null, + "nested_map": const { + "inner": "value" + } + } + ''') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNotNull); + final map = result!.value as Map; + expect(map['string'], 'hello world'); + expect(map['integer'], 42); + expect(map['double'], 3.14); + expect(map['boolean_true'], true); + expect(map['boolean_false'], false); + expect(map['null_value'], isNull); + expect(map['nested_map'], isA>()); + final nestedMap = map['nested_map'] as Map; + expect(nestedMap['inner'], 'value'); + }); + + test('should handle AdjacentStrings with non-evaluable children', () { + final identifierExpression = + _parseExpression('someVariable') as SimpleIdentifier; + final result = evaluateConstString(identifierExpression); + + expect(result, isNull); + }); + + test('should handle empty string in AdjacentStrings', () { + final expression = _parseExpression('""') as SimpleStringLiteral; + final result = evaluateConstString(expression); + + expect(result, ''); + }); + }); + + group('Edge cases and error conditions', () { + test('evaluate should handle very large integers', () { + final expression = _parseExpression('9223372036854775807') + as IntegerLiteral; // Max int64 + final result = evaluate(expression); + + expect(result!.value, 9223372036854775807); + }); + + test('evaluate should handle negative integers', () { + final expression = _parseExpression('-42') as PrefixExpression; + final result = evaluate(expression); + + expect(result, isNull); + }); + + test('evaluate should handle very small doubles', () { + final expression = _parseExpression('1e-100') as DoubleLiteral; + final result = evaluate(expression); + + expect(result!.value, 1e-100); + }); + + test('evaluateConstStringMap should handle map with duplicate keys', () { + final expression = + _parseExpression('const {"key": "first", "key": "second"}') + as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + final map = result!.value as Map; + expect(map['key'], 'second'); + }); + + test('should handle mixed string types in AdjacentStrings', () { + // Test with single and double quoted strings + final expression = + _parseExpression("'single' \"double\"") as AdjacentStrings; + final result = evaluateConstString(expression); + + expect(result, 'singledouble'); + }); + + test('should handle escape sequences in strings', () { + final expression = + _parseExpression(r'"hello\nworld"') as SimpleStringLiteral; + final result = evaluateConstString(expression); + + expect(result, 'hello\nworld'); + }); + + test('should handle const list', () { + final expression = _parseExpression('const [1, 2, 3]') as ListLiteral; + final result = evaluate(expression); + + expect(result, isNull); + }); + }); + + group('_checkChildren helper function edge cases', () { + test('should handle empty AdjacentStrings', () { + final expression = _parseExpression('"" ""') as AdjacentStrings; + final result = evaluateConstString(expression); + + expect(result, ''); + }); + + test('should handle single string in AdjacentStrings', () { + final expression = _parseExpression('"single"') as SimpleStringLiteral; + final result = evaluateConstString(expression); + + expect(result, 'single'); + }); + }); + + group('Additional coverage for completeness', () { + test('should handle const map with if elements', () { + final expression = _parseExpression('const {if (true) "key": "value"}') + as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should handle const map with for elements', () { + final expression = _parseExpression('const {for (var i in items) i: i}') + as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should handle map with null key', () { + final expression = + _parseExpression('const {null: "value"}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + + test('should handle map with boolean key', () { + final expression = + _parseExpression('const {true: "value"}') as SetOrMapLiteral; + final result = evaluateConstStringMap(expression); + + expect(result, isNull); + }); + }); +} + +/// Helper function to parse a single expression from a string +Expression _parseExpression(String code) { + final unit = parseString( + content: 'var x = $code;', + featureSet: FeatureSet.latestLanguageVersion(), + ).unit; + + final variableDeclaration = + unit.declarations.first as TopLevelVariableDeclaration; + final variable = variableDeclaration.variables.variables.first; + return variable.initializer!; +} From e75eff84670f63a59811f62c34939fdf8701ead3 Mon Sep 17 00:00:00 2001 From: Peter Trost Date: Thu, 12 Jun 2025 13:11:09 +0200 Subject: [PATCH 9/9] test: make test suite way more concise --- .../test/constant_evaluator_test.dart | 466 +++--------------- 1 file changed, 75 insertions(+), 391 deletions(-) diff --git a/pkgs/intl_translation/test/constant_evaluator_test.dart b/pkgs/intl_translation/test/constant_evaluator_test.dart index 3c53d4d8..eb3d87fe 100644 --- a/pkgs/intl_translation/test/constant_evaluator_test.dart +++ b/pkgs/intl_translation/test/constant_evaluator_test.dart @@ -10,162 +10,83 @@ import 'package:test/test.dart'; void main() { group('Constant class', () { - test('should wrap non-null values', () { - final constant = Constant('test'); - expect(constant.value, 'test'); - }); + test('should wrap values including null', () { + final stringConstant = Constant('test'); + expect(stringConstant.value, 'test'); - test('should wrap null values', () { - final constant = Constant(null); - expect(constant.value, isNull); + final nullConstant = Constant(null); + expect(nullConstant.value, isNull); }); - test('nullable should return null for null input', () { - final result = Constant.nullable(null); - expect(result, isNull); - }); + test('nullable should handle null and non-null values', () { + expect(Constant.nullable(null), isNull); - test('nullable should wrap non-null values', () { final result = Constant.nullable('test'); - expect(result, isNotNull); expect(result!.value, 'test'); }); }); group('evaluate function', () { - test('should evaluate SimpleStringLiteral', () { - final expression = - _parseExpression('"hello world"') as SimpleStringLiteral; - final result = evaluate(expression); - - expect(result, isNotNull); - expect(result!.value, 'hello world'); + test('should evaluate all basic literal types', () { + expect( + evaluate(_parseExpression('"hello"') as SimpleStringLiteral)!.value, + 'hello'); + expect(evaluate(_parseExpression('42') as IntegerLiteral)!.value, 42); + expect(evaluate(_parseExpression('3.14') as DoubleLiteral)!.value, 3.14); + expect(evaluate(_parseExpression('true') as BooleanLiteral)!.value, true); + expect(evaluate(_parseExpression('null') as NullLiteral)!.value, isNull); }); test('should evaluate AdjacentStrings', () { - final expression = - _parseExpression('"hello " "world"') as AdjacentStrings; - final result = evaluate(expression); - - expect(result, isNotNull); + final result = + evaluate(_parseExpression('"hello " "world"') as AdjacentStrings); expect(result!.value, 'hello world'); }); - test('should evaluate IntegerLiteral', () { - final expression = _parseExpression('42') as IntegerLiteral; - final result = evaluate(expression); - - expect(result, isNotNull); - expect(result!.value, 42); - }); - - test('should evaluate DoubleLiteral', () { - final expression = _parseExpression('3.14') as DoubleLiteral; - final result = evaluate(expression); - - expect(result, isNotNull); - expect(result!.value, 3.14); - }); - - test('should evaluate BooleanLiteral true', () { - final expression = _parseExpression('true') as BooleanLiteral; - final result = evaluate(expression); - - expect(result, isNotNull); - expect(result!.value, true); - }); - - test('should evaluate BooleanLiteral false', () { - final expression = _parseExpression('false') as BooleanLiteral; - final result = evaluate(expression); - - expect(result, isNotNull); - expect(result!.value, false); - }); - - test('should evaluate NullLiteral', () { - final expression = _parseExpression('null') as NullLiteral; - final result = evaluate(expression); - - expect(result, isNotNull); - expect(result!.value, isNull); - }); - - test('should evaluate const SetOrMapLiteral (map)', () { - final expression = _parseExpression('const {"key": "value", "num": 42}') - as SetOrMapLiteral; - final result = evaluate(expression); - - expect(result, isNotNull); - expect(result!.value, isA>()); - final map = result.value as Map; + test('should evaluate const maps', () { + final result = evaluate( + _parseExpression('const {"key": "value", "num": 42}') + as SetOrMapLiteral); + final map = result!.value as Map; expect(map['key'], 'value'); expect(map['num'], 42); }); test('should return null for unsupported expressions', () { - final expression = _parseExpression('someVariable') as SimpleIdentifier; - final result = evaluate(expression); - - expect(result, isNull); - }); - - test('should return null for function calls', () { - final expression = _parseExpression('someFunction()') as MethodInvocation; - final result = evaluate(expression); - - expect(result, isNull); + expect(evaluate(_parseExpression('someVariable') as SimpleIdentifier), + isNull); + expect(evaluate(_parseExpression('someFunction()') as MethodInvocation), + isNull); }); }); group('evaluateConstString function', () { - test('should evaluate SimpleStringLiteral', () { - final expression = _parseExpression('"hello"') as SimpleStringLiteral; - final result = evaluateConstString(expression); - - expect(result, 'hello'); - }); - - test('should evaluate AdjacentStrings with multiple parts', () { - final expression = - _parseExpression('"hello " "beautiful " "world"') as AdjacentStrings; - final result = evaluateConstString(expression); - - expect(result, 'hello beautiful world'); - }); - - test('should evaluate AdjacentStrings with empty string', () { - final expression = - _parseExpression('"hello" "" "world"') as AdjacentStrings; - final result = evaluateConstString(expression); - - expect(result, 'helloworld'); + test('should evaluate string literals and adjacent strings', () { + expect( + evaluateConstString( + _parseExpression('"hello"') as SimpleStringLiteral), + 'hello'); + expect( + evaluateConstString( + _parseExpression('"hello " "world"') as AdjacentStrings), + 'hello world'); }); test('should return null for non-string expressions', () { - final expression = _parseExpression('42') as IntegerLiteral; - final result = evaluateConstString(expression); - - expect(result, isNull); - }); - - test('should return null for string interpolation', () { - final expression = - _parseExpression(r'"hello ${variable}"') as StringInterpolation; - final result = evaluateConstString(expression); - - expect(result, isNull); + expect(evaluateConstString(_parseExpression('42') as IntegerLiteral), + isNull); + expect( + evaluateConstString( + _parseExpression(r'"hello ${variable}"') as StringInterpolation), + isNull); }); }); group('evaluateConstStringMap function', () { - test('should evaluate const map with string keys and mixed values', () { - final expression = _parseExpression( + test('should evaluate const maps with string keys', () { + final result = evaluateConstStringMap(_parseExpression( 'const {"str": "value", "num": 42, "bool": true, "nil": null}') - as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNotNull); + as SetOrMapLiteral); final map = result!.value as Map; expect(map['str'], 'value'); expect(map['num'], 42); @@ -173,276 +94,39 @@ void main() { expect(map['nil'], isNull); }); - test('should evaluate const empty map', () { - final expression = - _parseExpression('const {}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNotNull); - expect(result!.value, isA>()); - expect((result.value).isEmpty, isTrue); - }); - - test('should return null for non-const map', () { - final expression = - _parseExpression('{"key": "value"}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should return null for const set', () { - final expression = - _parseExpression('const {"item1", "item2"}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should return null for map with spread elements', () { - final expression = - _parseExpression('const {...other}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should return null for map with non-evaluable keys', () { - final expression = - _parseExpression('const {someVariable: "value"}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should return null for map with non-evaluable values', () { - final expression = - _parseExpression('const {"key": someVariable}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should return null for map with non-string keys', () { - final expression = - _parseExpression('const {42: "value"}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should handle nested const maps', () { - final expression = - _parseExpression('const {"outer": const {"inner": "value"}}') - as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNotNull); + test('should handle nested maps and adjacent strings', () { + final result = evaluateConstStringMap( + _parseExpression('const {"outer": const {"inner": "hello " "world"}}') + as SetOrMapLiteral); final outerMap = result!.value as Map; - expect(outerMap['outer'], isA>()); final innerMap = outerMap['outer'] as Map; - expect(innerMap['inner'], 'value'); - }); - }); - - group('Recursive and complex scenarios', () { - test('should handle nested AdjacentStrings in map values', () { - final expression = _parseExpression('const {"key": "hello " "world"}') - as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNotNull); - final map = result!.value as Map; - expect(map['key'], 'hello world'); - }); - - test('should handle nested AdjacentStrings in map keys', () { - final expression = _parseExpression('const {"hello " "world": "value"}') - as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNotNull); - final map = result!.value as Map; - expect(map['hello world'], 'value'); - }); - - test('should handle deeply nested const structures', () { - final expression = _parseExpression(''' - const { - "level1": const { - "level2": const { - "level3": "deep " "value" - } - } - } - ''') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNotNull); - final level1 = result!.value as Map; - final level2 = level1['level1'] as Map; - final level3 = level2['level2'] as Map; - expect(level3['level3'], 'deep value'); - }); - - test('should handle AdjacentStrings with many parts', () { - final expression = - _parseExpression('"a" "b" "c" "d" "e"') as AdjacentStrings; - final result = evaluateConstString(expression); - - expect(result, 'abcde'); - }); - - test('should handle complex map with all supported literal types', () { - final expression = _parseExpression(''' - const { - "string": "hello " "world", - "integer": 42, - "double": 3.14, - "boolean_true": true, - "boolean_false": false, - "null_value": null, - "nested_map": const { - "inner": "value" - } - } - ''') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNotNull); - final map = result!.value as Map; - expect(map['string'], 'hello world'); - expect(map['integer'], 42); - expect(map['double'], 3.14); - expect(map['boolean_true'], true); - expect(map['boolean_false'], false); - expect(map['null_value'], isNull); - expect(map['nested_map'], isA>()); - final nestedMap = map['nested_map'] as Map; - expect(nestedMap['inner'], 'value'); - }); - - test('should handle AdjacentStrings with non-evaluable children', () { - final identifierExpression = - _parseExpression('someVariable') as SimpleIdentifier; - final result = evaluateConstString(identifierExpression); - - expect(result, isNull); - }); - - test('should handle empty string in AdjacentStrings', () { - final expression = _parseExpression('""') as SimpleStringLiteral; - final result = evaluateConstString(expression); - - expect(result, ''); - }); - }); - - group('Edge cases and error conditions', () { - test('evaluate should handle very large integers', () { - final expression = _parseExpression('9223372036854775807') - as IntegerLiteral; // Max int64 - final result = evaluate(expression); - - expect(result!.value, 9223372036854775807); - }); - - test('evaluate should handle negative integers', () { - final expression = _parseExpression('-42') as PrefixExpression; - final result = evaluate(expression); - - expect(result, isNull); - }); - - test('evaluate should handle very small doubles', () { - final expression = _parseExpression('1e-100') as DoubleLiteral; - final result = evaluate(expression); - - expect(result!.value, 1e-100); - }); - - test('evaluateConstStringMap should handle map with duplicate keys', () { - final expression = - _parseExpression('const {"key": "first", "key": "second"}') - as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - final map = result!.value as Map; - expect(map['key'], 'second'); - }); - - test('should handle mixed string types in AdjacentStrings', () { - // Test with single and double quoted strings - final expression = - _parseExpression("'single' \"double\"") as AdjacentStrings; - final result = evaluateConstString(expression); - - expect(result, 'singledouble'); - }); - - test('should handle escape sequences in strings', () { - final expression = - _parseExpression(r'"hello\nworld"') as SimpleStringLiteral; - final result = evaluateConstString(expression); - - expect(result, 'hello\nworld'); - }); - - test('should handle const list', () { - final expression = _parseExpression('const [1, 2, 3]') as ListLiteral; - final result = evaluate(expression); - - expect(result, isNull); - }); - }); - - group('_checkChildren helper function edge cases', () { - test('should handle empty AdjacentStrings', () { - final expression = _parseExpression('"" ""') as AdjacentStrings; - final result = evaluateConstString(expression); - - expect(result, ''); - }); - - test('should handle single string in AdjacentStrings', () { - final expression = _parseExpression('"single"') as SimpleStringLiteral; - final result = evaluateConstString(expression); - - expect(result, 'single'); - }); - }); - - group('Additional coverage for completeness', () { - test('should handle const map with if elements', () { - final expression = _parseExpression('const {if (true) "key": "value"}') - as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should handle const map with for elements', () { - final expression = _parseExpression('const {for (var i in items) i: i}') - as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should handle map with null key', () { - final expression = - _parseExpression('const {null: "value"}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); - }); - - test('should handle map with boolean key', () { - final expression = - _parseExpression('const {true: "value"}') as SetOrMapLiteral; - final result = evaluateConstStringMap(expression); - - expect(result, isNull); + expect(innerMap['inner'], 'hello world'); + }); + + test('should return null for invalid cases', () { + // Non-const map + expect( + evaluateConstStringMap( + _parseExpression('{"key": "value"}') as SetOrMapLiteral), + isNull); + + // Const set (not map) + expect( + evaluateConstStringMap( + _parseExpression('const {"item1", "item2"}') as SetOrMapLiteral), + isNull); + + // Non-string keys + expect( + evaluateConstStringMap( + _parseExpression('const {42: "value"}') as SetOrMapLiteral), + isNull); + + // Non-evaluable values + expect( + evaluateConstStringMap(_parseExpression('const {"key": someVariable}') + as SetOrMapLiteral), + isNull); }); }); }