Skip to content

Commit bdb94c2

Browse files
Googlercopybara-github
authored andcommitted
No public description
PiperOrigin-RevId: 781004395
1 parent 42c4932 commit bdb94c2

18 files changed

+390
-115
lines changed

pkgs/intl_translation/CHANGELOG.md

Lines changed: 7 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,12 @@
1+
## 0.21.0-wip
2+
* BREAKING CHANGE: Update `dart_style` to `^3.0.0`
3+
* Allow analyzer `>=6.3.0 <8.0.0`
4+
* Throw if the main argument in plurals and selects does not match the argument list.
5+
* Fix constant evaluator.
6+
17
## 0.20.1
28
* Add topics to `pubspec.yaml`
3-
* Update to `dart_style `2.3.7`. `bin/make_examples_const.dart` and
9+
* Update to `dart_style` `2.3.7`. `bin/make_examples_const.dart` and
410
`rewrite_intl_messages.dart` will now look for a surrounding
511
`.dart_tool/package_config.json` file to infer the language version of the
612
files updated by the script.
Lines changed: 106 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,106 @@
1+
// Copyright (c) 2025, the Dart project authors. Please see the AUTHORS file
2+
// for details. All rights reserved. Use of this source code is governed by a
3+
// BSD-style license that can be found in the LICENSE file.
4+
5+
import 'package:analyzer/dart/ast/ast.dart';
6+
7+
/// Needed to wrap `null` as a valid constant value
8+
class Constant<T extends Object?> {
9+
final T value;
10+
11+
Constant(this.value);
12+
13+
static Constant<T>? nullable<T extends Object>(T? o) =>
14+
o == null ? null : Constant(o);
15+
}
16+
17+
Constant? evaluate(Expression expression) {
18+
return switch (expression) {
19+
AdjacentStrings() => Constant.nullable(evaluateConstString(expression)),
20+
SimpleStringLiteral() => Constant(expression.value),
21+
IntegerLiteral() => Constant(expression.value),
22+
DoubleLiteral() => Constant(expression.value),
23+
BooleanLiteral() => Constant(expression.value),
24+
NullLiteral() => Constant(null),
25+
SetOrMapLiteral() => evaluateConstStringMap(expression),
26+
PrefixExpression() => evaluatePrefixed(expression),
27+
ListLiteral() => evaluateListLiteral(expression),
28+
_ => null,
29+
};
30+
}
31+
32+
Constant<Iterable<Object?>>? evaluateListLiteral(ListLiteral list) {
33+
if (!list.isConst) {
34+
return null;
35+
}
36+
if (list.elements.any(
37+
(element) => element is! Expression,
38+
)) {
39+
return null;
40+
}
41+
final evaluatedElements = list.elements.whereType<Expression>().map(
42+
(element) => evaluate(element)?.value,
43+
);
44+
if (evaluatedElements.any((element) => element == null)) {
45+
return null;
46+
}
47+
return Constant(evaluatedElements);
48+
}
49+
50+
Constant<num>? evaluatePrefixed(PrefixExpression expression) {
51+
final operator = expression.operator;
52+
if (operator.lexeme != '-') {
53+
return null;
54+
}
55+
final constant = evaluate(expression.operand);
56+
final numValue = constant?.value;
57+
if (numValue is! num) {
58+
return null;
59+
}
60+
return Constant(-1 * numValue);
61+
}
62+
63+
String? evaluateConstString(Expression expression) => switch (expression) {
64+
SimpleStringLiteral() => expression.value,
65+
AdjacentStrings() => _checkChildren(expression.strings)?.join(),
66+
_ => null,
67+
};
68+
69+
Iterable<String>? _checkChildren(Iterable<StringLiteral> strings) {
70+
final constExpressions = strings.map(
71+
(string) => evaluateConstString(string),
72+
);
73+
return constExpressions.any(
74+
(string) => string == null,
75+
)
76+
? null
77+
: constExpressions.whereType();
78+
}
79+
80+
Constant<Map>? evaluateConstStringMap(SetOrMapLiteral map) {
81+
if (!map.isConst) {
82+
return null;
83+
}
84+
if (map.elements.any(
85+
(element) => element is! MapLiteralEntry,
86+
)) {
87+
return null;
88+
}
89+
final evaluatedEntries = map.elements.whereType<MapLiteralEntry>().map(
90+
(literalEntry) =>
91+
(evaluate(literalEntry.key), evaluate(literalEntry.value)),
92+
);
93+
if (evaluatedEntries
94+
.any((element) => element.$1 == null || element.$2 == null)) {
95+
return null;
96+
}
97+
var extractedValues = evaluatedEntries.map(
98+
(literalEntry) => MapEntry(literalEntry.$1!.value, literalEntry.$2!.value),
99+
);
100+
if (extractedValues.any((element) => element.key is! String)) {
101+
return null;
102+
}
103+
return Constant(Map<String, dynamic>.fromEntries(extractedValues.map(
104+
(e) => MapEntry(e.key as String, e.value),
105+
)));
106+
}

pkgs/intl_translation/lib/src/messages/main_message.dart

Lines changed: 1 addition & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -33,7 +33,7 @@ class MainMessage extends ComplexMessage {
3333
/// Verify that this looks like a correct Intl.message invocation.
3434
static void checkValidity(
3535
MethodInvocation node,
36-
List arguments,
36+
List<Expression> arguments,
3737
String? outerName,
3838
List<FormalParameter> outerArgs, {
3939
bool nameAndArgsGenerated = false,

pkgs/intl_translation/lib/src/messages/message.dart

Lines changed: 7 additions & 29 deletions
Original file line numberDiff line numberDiff line change
@@ -35,11 +35,10 @@ library intl_message;
3535
import 'dart:convert';
3636

3737
import 'package:analyzer/dart/ast/ast.dart';
38-
// ignore: implementation_imports
39-
import 'package:analyzer/src/dart/ast/constant_evaluator.dart';
4038

4139
import 'complex_message.dart';
4240
import 'composite_message.dart';
41+
import 'constant_evaluator.dart';
4342
import 'literal_string_message.dart';
4443
import 'message_extraction_exception.dart';
4544
import 'variable_substitution_message.dart';
@@ -75,26 +74,6 @@ abstract class Message {
7574
/// The name of the top-level [MainMessage].
7675
String get name => parent == null ? '<unnamed>' : parent!.name;
7776

78-
static final _evaluator = ConstantEvaluator();
79-
80-
static String? _evaluateAsString(expression) {
81-
var result = expression.accept(_evaluator);
82-
if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! String) {
83-
return null;
84-
} else {
85-
return result;
86-
}
87-
}
88-
89-
static Map? _evaluateAsMap(Expression expression) {
90-
var result = expression.accept(_evaluator);
91-
if (result == ConstantEvaluator.NOT_A_CONSTANT || result is! Map) {
92-
return null;
93-
} else {
94-
return result;
95-
}
96-
}
97-
9877
/// Verify that the args argument matches the method parameters and
9978
/// isn't, e.g. passing string names instead of the argument values.
10079
static bool checkArgs(NamedExpression? args, List<String> parameterNames) {
@@ -141,7 +120,7 @@ abstract class Message {
141120
/// for messages with parameters.
142121
static void checkValidity(
143122
MethodInvocation node,
144-
List arguments,
123+
List<Expression> arguments,
145124
String? outerName,
146125
List<FormalParameter> outerArgs, {
147126
bool nameAndArgsGenerated = false,
@@ -165,7 +144,7 @@ abstract class Message {
165144
' e.g. args: $parameterNames');
166145
}
167146

168-
var nameNamedExps = arguments
147+
final nameNamedExps = arguments
169148
.whereType<NamedExpression>()
170149
.where((arg) => arg.name.label.name == 'name')
171150
.map((e) => e.expression);
@@ -177,7 +156,7 @@ abstract class Message {
177156
if (nameNamedExps.isEmpty) {
178157
if (!hasParameters) {
179158
// No name supplied, no parameters. Use the message as the name.
180-
var name = _evaluateAsString(arguments[0]);
159+
var name = evaluateConstString(arguments[0]);
181160
messageName = name;
182161
outerName = name;
183162
} else {
@@ -195,7 +174,7 @@ abstract class Message {
195174
}
196175
} else {
197176
// Name argument is supplied, use it.
198-
var name = _evaluateAsString(nameNamedExps.first);
177+
var name = evaluateConstString(nameNamedExps.first);
199178
messageName = name;
200179
givenName = name;
201180
}
@@ -227,7 +206,7 @@ abstract class Message {
227206
.map((each) => each.expression)
228207
.toList();
229208
for (var arg in values) {
230-
if (_evaluateAsString(arg) == null) {
209+
if (evaluateConstString(arg) == null) {
231210
throw MessageExtractionException(
232211
'Intl.message arguments must be string literals: $arg');
233212
}
@@ -245,8 +224,7 @@ abstract class Message {
245224
if (examples.isNotEmpty) {
246225
var example = examples.first;
247226
if (example is SetOrMapLiteral) {
248-
var map = _evaluateAsMap(example);
249-
if (map == null) {
227+
if (evaluateConstStringMap(example) == null) {
250228
throw MessageExtractionException(
251229
'Examples must be a const Map literal.');
252230
} else if (example.constKeyword == null) {

pkgs/intl_translation/lib/src/messages/submessages/submessage.dart

Lines changed: 8 additions & 1 deletion
Original file line numberDiff line numberDiff line change
@@ -95,7 +95,14 @@ abstract class SubMessage extends ComplexMessage {
9595
List toJson() {
9696
var json = [];
9797
json.add(dartMessageName);
98-
json.add(arguments.indexOf(mainArgument));
98+
var indexOf = arguments.indexOf(mainArgument);
99+
if (indexOf == -1) {
100+
// This is an error as this should have been checked in
101+
// lib/visitors/plural_gender_visitor.dart already.
102+
throw ArgumentError('The argument `$mainArgument` could not be found in '
103+
'`$arguments`.');
104+
}
105+
json.add(indexOf);
99106
for (var arg in codeAttributeNames) {
100107
json.add(this[arg]?.toJson());
101108
}

pkgs/intl_translation/lib/visitors/message_finding_visitor.dart

Lines changed: 3 additions & 6 deletions
Original file line numberDiff line numberDiff line change
@@ -4,10 +4,9 @@
44

55
import 'package:analyzer/dart/ast/ast.dart';
66
import 'package:analyzer/dart/ast/visitor.dart';
7-
// ignore: implementation_imports
8-
import 'package:analyzer/src/dart/ast/constant_evaluator.dart';
97

108
import '../extract_messages.dart';
9+
import '../src/messages/constant_evaluator.dart';
1110
import '../src/messages/main_message.dart';
1211
import '../src/messages/message.dart';
1312
import '../src/messages/message_extraction_exception.dart';
@@ -288,10 +287,8 @@ class MessageFindingVisitor extends GeneralizingAstVisitor {
288287
for (var namedExpr in arguments.whereType<NamedExpression>()) {
289288
var name = namedExpr.name.label.name;
290289
var exp = namedExpr.expression;
291-
var basicValue = exp.accept(ConstantEvaluator());
292-
var value = basicValue == ConstantEvaluator.NOT_A_CONSTANT
293-
? exp.toString()
294-
: basicValue;
290+
var constant = evaluate(exp);
291+
var value = constant == null ? exp.toString() : constant.value;
295292
setAttribute(message, name, value);
296293
}
297294
// We only rewrite messages with parameters, otherwise we use the literal

pkgs/intl_translation/lib/visitors/plural_gender_visitor.dart

Lines changed: 7 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -137,6 +137,13 @@ class PluralAndGenderVisitor extends SimpleAstVisitor<void> {
137137
extraction.warnings.add(errString);
138138
}
139139

140+
if (!message.arguments.contains(message.mainArgument)) {
141+
throw Exception(
142+
'Argument `${message.mainArgument}` could not be found in '
143+
'${message.arguments} while processing $node. The parent argument '
144+
'name must match the one in the `Intl.` call.');
145+
}
146+
140147
return message;
141148
}
142149
}

pkgs/intl_translation/pubspec.yaml

Lines changed: 3 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -1,5 +1,5 @@
11
name: intl_translation
2-
version: 0.20.1
2+
version: 0.21.0-wip
33
description: >-
44
Contains code to deal with internationalized/localized messages,
55
date and number formatting and parsing, bi-directional text, and
@@ -15,9 +15,9 @@ environment:
1515
sdk: ^3.0.0
1616

1717
dependencies:
18-
analyzer: ^6.3.0
18+
analyzer: ">=6.3.0 <8.0.0"
1919
args: ^2.0.0
20-
dart_style: ^2.0.0
20+
dart_style: ^3.0.0
2121
intl: '>=0.19.0 <0.21.0'
2222
package_config: ^2.1.0
2323
path: ^1.0.0

0 commit comments

Comments
 (0)