Chromium Code Reviews
chromiumcodereview-hr@appspot.gserviceaccount.com (chromiumcodereview-hr) | Please choose your nickname with Settings | Help | Chromium Project | Gerrit Changes | Sign out
(11)

Unified Diff: packages/intl/lib/src/intl_message.dart

Issue 2989763002: Update charted to 0.4.8 and roll (Closed)
Patch Set: Removed Cutch from list of reviewers Created 3 years, 5 months ago
Use n/p to move between diff chunks; N/P to move between comments. Draft comments are only viewable by you.
Jump to:
View side-by-side diff with in-line comments
Download patch
« no previous file with comments | « packages/intl/lib/src/intl_helpers.dart ('k') | packages/intl/lib/src/lazy_locale_data.dart » ('j') | no next file with comments »
Expand Comments ('e') | Collapse Comments ('c') | Show Comments Hide Comments ('s')
Index: packages/intl/lib/src/intl_message.dart
diff --git a/packages/intl/lib/src/intl_message.dart b/packages/intl/lib/src/intl_message.dart
deleted file mode 100644
index 3df4a6194965cd19377aa7175b27d36793a8c97d..0000000000000000000000000000000000000000
--- a/packages/intl/lib/src/intl_message.dart
+++ /dev/null
@@ -1,767 +0,0 @@
-// Copyright (c) 2013, 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.
-
-/**
- * This provides classes to represent the internal structure of the
- * arguments to `Intl.message`. It is used when parsing sources to extract
- * messages or to generate code for message substitution. Normal programs
- * using Intl would not import this library.
- *
- * While it's written
- * in a somewhat abstract way, it has some assumptions about ICU-style
- * message syntax for parameter substitutions, choices, selects, etc.
- *
- * For example, if we have the message
- * plurals(num) => Intl.message("""${Intl.plural(num,
- * zero : 'Is zero plural?',
- * one : 'This is singular.',
- * other : 'This is plural ($num).')
- * }""",
- * name: "plurals", args: [num], desc: "Basic plurals");
- * That is represented as a MainMessage which has only one message component, a
- * Plural, but also has a name, list of arguments, and a description.
- * The Plural has three different clauses. The `zero` clause is
- * a LiteralString containing 'Is zero plural?'. The `other` clause is a
- * CompositeMessage containing three pieces, a LiteralString for
- * 'This is plural (', a VariableSubstitution for `num`. amd a LiteralString
- * for '.)'.
- *
- * This representation isn't used at runtime. Rather, we read some format
- * from a translation file, parse it into these objects, and they are then
- * used to generate the code representation above.
- */
-library intl_message;
-
-import 'package:analyzer/analyzer.dart';
-
-/** A default function for the [Message.expanded] method. */
-_nullTransform(msg, chunk) => chunk;
-
-/**
- * An abstract superclass for Intl.message/plural/gender calls in the
- * program's source text. We
- * assemble these into objects that can be used to write out some translation
- * format and can also print themselves into code.
- */
-abstract class Message {
-
- /**
- * All [Message]s except a [MainMessage] are contained inside some parent,
- * terminating at an Intl.message call which supplies the arguments we
- * use for variable substitutions.
- */
- Message parent;
-
- Message(this.parent);
-
- /**
- * We find the arguments from the top-level [MainMessage] and use those to
- * do variable substitutions. [MainMessage] overrides this to return
- * the actual arguments.
- */
- get arguments => parent == null ? const [] : parent.arguments;
-
- /**
- * We find the examples from the top-level [MainMessage] and use those
- * when writing out variables. [MainMessage] overrides this to return
- * the actual examples.
- */
- get examples => parent == null ? const [] : parent.examples;
-
- /**
- * The name of the top-level [MainMessage].
- */
- String get name => parent == null ? '<unnamed>' : parent.name;
-
- String checkValidity(MethodInvocation node, List arguments, String outerName,
- FormalParameterList outerArgs) {
- var hasArgs = arguments.any(
- (each) => each is NamedExpression && each.name.label.name == 'args');
- var hasParameters = !outerArgs.parameters.isEmpty;
- if (!hasArgs && hasParameters) {
- return "The 'args' argument for Intl.message must be specified";
- }
-
- var messageName = arguments.firstWhere((eachArg) =>
- eachArg is NamedExpression && eachArg.name.label.name == 'name',
- orElse: () => null);
- if (messageName == null) {
- return "The 'name' argument for Intl.message must be specified";
- }
- if (messageName.expression is! SimpleStringLiteral) {
- return "The 'name' argument for Intl.message must be a simple string "
- "literal.";
- }
- var hasOuterName = outerName != null;
- var givenName = messageName.expression.value;
- var simpleMatch = outerName == givenName;
- ClassDeclaration classNode(n) {
- if (n == null) return null;
- if (n is ClassDeclaration) return n;
- return classNode(n.parent);
- }
- var classDeclaration = classNode(node);
- var classPlusMethod = classDeclaration == null
- ? null
- : "${classDeclaration.name.token.toString()}_$outerName";
- var classMatch = classPlusMethod != null && (givenName == classPlusMethod);
- if (!(hasOuterName && (simpleMatch || classMatch))) {
- return "The 'name' argument for Intl.message must match either"
- "the name of the containing function or <className>_<methodName> ("
- "'$givenName' vs. '$outerName')";
- }
- var simpleArguments = arguments.where((each) => each is NamedExpression &&
- ["desc", "name"].contains(each.name.label.name));
- var values = simpleArguments.map((each) => each.expression).toList();
- for (var arg in values) {
- if (arg is! StringLiteral) {
- return ("Intl.message arguments must be string literals: $arg");
- }
- }
- return null;
- }
-
- /**
- * Turn a value, typically read from a translation file or created out of an
- * AST for a source program, into the appropriate
- * subclass. We expect to get literal Strings, variable substitutions
- * represented by integers, things that are already MessageChunks and
- * lists of the same.
- */
- static Message from(value, Message parent) {
- if (value is String) return new LiteralString(value, parent);
- if (value is int) return new VariableSubstitution(value, parent);
- if (value is Iterable) {
- if (value.length == 1) return Message.from(value[0], parent);
- var result = new CompositeMessage([], parent);
- var items = value.map((x) => from(x, result)).toList();
- result.pieces.addAll(items);
- return result;
- }
- // We assume this is already a Message.
- value.parent = parent;
- return value;
- }
-
- /**
- * Return a string representation of this message for use in generated Dart
- * code.
- */
- String toCode();
-
- /**
- * Escape the string for use in generated Dart code and validate that it
- * doesn't doesn't contain any illegal interpolations. We only allow
- * simple variables ("$foo", but not "${foo}") and Intl.gender/plural
- * calls.
- */
- String escapeAndValidateString(String value) {
- const escapes = const {
- r"\": r"\\",
- '"': r'\"',
- "\b": r"\b",
- "\f": r"\f",
- "\n": r"\n",
- "\r": r"\r",
- "\t": r"\t",
- "\v": r"\v",
- "'": r"\'",
- };
-
- _escape(String s) => (escapes[s] == null) ? s : escapes[s];
-
- var escaped = value.splitMapJoin("", onNonMatch: _escape);
-
- // We don't allow any ${} expressions, only $variable to avoid malicious
- // code. Disallow any usage of "${". If that makes a false positive
- // on a translation that legitimately contains "\\${" or other variations,
- // we'll live with that rather than risk a false negative.
- var validInterpolations = new RegExp(r"(\$\w+)|(\${\w+})");
- var validMatches = validInterpolations.allMatches(escaped);
- escapeInvalidMatches(Match m) {
- var valid = validMatches.any((x) => x.start == m.start);
- if (valid) {
- return m.group(0);
- } else {
- return "\\${m.group(0)}";
- }
- }
- return escaped.replaceAllMapped("\$", escapeInvalidMatches);
- }
-
- /**
- * Expand this string out into a printed form. The function [f] will be
- * applied to any sub-messages, allowing this to be used to generate a form
- * suitable for a wide variety of translation file formats.
- */
- String expanded([Function f]);
-}
-
-/**
- * Abstract class for messages with internal structure, representing the
- * main Intl.message call, plurals, and genders.
- */
-abstract class ComplexMessage extends Message {
- ComplexMessage(parent) : super(parent);
-
- /**
- * When we create these from strings or from AST nodes, we want to look up and
- * set their attributes by string names, so we override the indexing operators
- * so that they behave like maps with respect to those attribute names.
- */
- operator [](x);
-
- /**
- * When we create these from strings or from AST nodes, we want to look up and
- * set their attributes by string names, so we override the indexing operators
- * so that they behave like maps with respect to those attribute names.
- */
- operator []=(x, y);
-
- List<String> get attributeNames;
-
- /**
- * Return the name of the message type, as it will be generated into an
- * ICU-type format. e.g. choice, select
- */
- String get icuMessageName;
-
- /**
- * Return the message name we would use for this when doing Dart code
- * generation, e.g. "Intl.plural".
- */
- String get dartMessageName;
-}
-
-/**
- * This represents a message chunk that is a list of multiple sub-pieces,
- * each of which is in turn a [Message].
- */
-class CompositeMessage extends Message {
- List<Message> pieces;
-
- CompositeMessage.withParent(parent) : super(parent);
- CompositeMessage(this.pieces, ComplexMessage parent) : super(parent) {
- pieces.forEach((x) => x.parent = this);
- }
- toCode() => pieces.map((each) => each.toCode()).join('');
- toString() => "CompositeMessage(" + pieces.toString() + ")";
- String expanded([Function f = _nullTransform]) =>
- pieces.map((chunk) => f(this, chunk)).join("");
-}
-
-/** Represents a simple constant string with no dynamic elements. */
-class LiteralString extends Message {
- String string;
- LiteralString(this.string, Message parent) : super(parent);
- toCode() => escapeAndValidateString(string);
- toString() => "Literal($string)";
- String expanded([Function f = _nullTransform]) => f(this, string);
-}
-
-/**
- * Represents an interpolation of a variable value in a message. We expect
- * this to be specified as an [index] into the list of variables, or else
- * as the name of a variable that exists in [arguments] and we will
- * compute the variable name or the index based on the value of the other.
- */
-class VariableSubstitution extends Message {
- VariableSubstitution(this._index, Message parent) : super(parent);
-
- /**
- * Create a substitution based on the name rather than the index. The name
- * may have been used as all upper-case in the translation tool, so we
- * save it separately and look it up case-insensitively once the parent
- * (and its arguments) are definitely available.
- */
- VariableSubstitution.named(String name, Message parent) : super(parent) {
- _variableNameUpper = name.toUpperCase();
- }
-
- /** The index in the list of parameters of the containing function. */
- int _index;
- int get index {
- if (_index != null) return _index;
- if (arguments.isEmpty) return null;
- // We may have been given an all-uppercase version of the name, so compare
- // case-insensitive.
- _index = arguments
- .map((x) => x.toUpperCase())
- .toList()
- .indexOf(_variableNameUpper);
- if (_index == -1) {
- throw new ArgumentError(
- "Cannot find parameter named '$_variableNameUpper' in "
- "message named '$name'. Available "
- "parameters are $arguments");
- }
- return _index;
- }
-
- /**
- * The variable name we get from parsing. This may be an all uppercase version
- * of the Dart argument name.
- */
- String _variableNameUpper;
-
- /**
- * The name of the variable in the parameter list of the containing function.
- * Used when generating code for the interpolation.
- */
- String get variableName =>
- _variableName == null ? _variableName = arguments[index] : _variableName;
- String _variableName;
- // Although we only allow simple variable references, we always enclose them
- // in curly braces so that there's no possibility of ambiguity with
- // surrounding text.
- toCode() => "\${${variableName}}";
- toString() => "VariableSubstitution($index)";
- String expanded([Function f = _nullTransform]) => f(this, index);
-}
-
-class MainMessage extends ComplexMessage {
- MainMessage() : super(null);
-
- /**
- * All the pieces of the message. When we go to print, these will
- * all be expanded appropriately. The exact form depends on what we're
- * printing it for See [expanded], [toCode].
- */
- List<Message> messagePieces = [];
-
- /** Verify that this looks like a correct Intl.message invocation. */
- String checkValidity(MethodInvocation node, List arguments, String outerName,
- FormalParameterList outerArgs) {
- if (arguments.first is! StringLiteral) {
- return "Intl.message messages must be string literals";
- }
-
- return super.checkValidity(node, arguments, outerName, outerArgs);
- }
-
- void addPieces(List<Message> messages) {
- for (var each in messages) {
- messagePieces.add(Message.from(each, this));
- }
- }
-
- /** The description provided in the Intl.message call. */
- String description;
-
- /** The examples from the Intl.message call */
- Map<String, dynamic> examples;
-
- /**
- * A field to disambiguate two messages that might have exactly the
- * same text. The two messages will also need different names, but
- * this can be used by machine translation tools to distinguish them.
- */
- String meaning;
-
- /**
- * The name, which may come from the function name, from the arguments
- * to Intl.message, or we may just re-use the message.
- */
- String _name;
-
- /**
- * A placeholder for any other identifier that the translation format
- * may want to use.
- */
- String id;
-
- /** The arguments list from the Intl.message call. */
- List arguments;
-
- /**
- * When generating code, we store translations for each locale
- * associated with the original message.
- */
- Map<String, String> translations = new Map();
-
- /**
- * If the message was not given a name, we use the entire message string as
- * the name.
- */
- String get name => _name == null ? computeName() : _name;
- set name(String newName) {
- _name = newName;
- }
-
- String computeName() => name = expanded((msg, chunk) => "");
-
- /**
- * Return the full message, with any interpolation expressions transformed
- * by [f] and all the results concatenated. The chunk argument to [f] may be
- * either a String, an int or an object representing a more complex
- * message entity.
- * See [messagePieces].
- */
- String expanded([Function f = _nullTransform]) =>
- messagePieces.map((chunk) => f(this, chunk)).join("");
-
- /**
- * Record the translation for this message in the given locale, after
- * suitably escaping it.
- */
- void addTranslation(String locale, Message translated) {
- translated.parent = this;
- translations[locale] = translated.toCode();
- }
-
- toCode() =>
- throw new UnsupportedError("MainMessage.toCode requires a locale");
-
- /**
- * Generate code for this message, expecting it to be part of a map
- * keyed by name with values the function that calls Intl.message.
- */
- String toCodeForLocale(String locale) {
- var out = new StringBuffer()
- ..write('static $name(')
- ..write(arguments.join(", "))
- ..write(') => "')
- ..write(translations[locale])
- ..write('";');
- return out.toString();
- }
-
- /**
- * The AST node will have the attribute names as strings, so we translate
- * between those and the fields of the class.
- */
- void operator []=(attributeName, value) {
- switch (attributeName) {
- case "desc":
- description = value;
- return;
- case "examples":
- examples = value;
- return;
- case "name":
- name = value;
- return;
- // We use the actual args from the parser rather than what's given in the
- // arguments to Intl.message.
- case "args":
- return;
- case "meaning":
- meaning = value;
- return;
- default:
- return;
- }
- }
-
- /**
- * The AST node will have the attribute names as strings, so we translate
- * between those and the fields of the class.
- */
- operator [](attributeName) {
- switch (attributeName) {
- case "desc":
- return description;
- case "examples":
- return examples;
- case "name":
- return name;
- // We use the actual args from the parser rather than what's given in the
- // arguments to Intl.message.
- case "args":
- return [];
- case "meaning":
- return meaning;
- default:
- return null;
- }
- }
-
- // This is the top-level construct, so there's no meaningful ICU name.
- get icuMessageName => '';
-
- get dartMessageName => "message";
-
- /** The parameters that the Intl.message call may provide. */
- get attributeNames => const ["name", "desc", "examples", "args", "meaning"];
-
- String toString() =>
- "Intl.message(${expanded()}, $name, $description, $examples, $arguments)";
-}
-
-/**
- * An abstract class to represent sub-sections of a message, primarily
- * plurals and genders.
- */
-abstract class SubMessage extends ComplexMessage {
- SubMessage() : super(null);
-
- /**
- * Creates the sub-message, given a list of [clauses] in the sort of form
- * that we're likely to get them from parsing a translation file format,
- * as a list of [key, value] where value may in turn be a list.
- */
- SubMessage.from(this.mainArgument, List clauses, parent) : super(parent) {
- for (var clause in clauses) {
- this[clause.first] = (clause.last is List) ? clause.last : [clause.last];
- }
- }
-
- toString() => expanded();
-
- /**
- * The name of the main argument, which is expected to have the value
- * which is one of [attributeNames] and is used to decide which clause to use.
- */
- String mainArgument;
-
- /**
- * Return the arguments that affect this SubMessage as a map of
- * argument names and values.
- */
- Map argumentsOfInterestFor(MethodInvocation node) {
- var basicArguments = node.argumentList.arguments;
- var others = basicArguments.where((each) => each is NamedExpression);
- return new Map.fromIterable(others,
- key: (node) => node.name.label.token.value(),
- value: (node) => node.expression);
- }
-
- /**
- * Return the list of attribute names to use when generating code. This
- * may be different from [attributeNames] if there are multiple aliases
- * that map to the same clause.
- */
- List<String> get codeAttributeNames;
-
- String expanded([Function transform = _nullTransform]) {
- fullMessageForClause(key) =>
- key + '{' + transform(parent, this[key]).toString() + '}';
- var clauses = attributeNames
- .where((key) => this[key] != null)
- .map(fullMessageForClause)
- .toList();
- return "{$mainArgument,$icuMessageName, ${clauses.join("")}}";
- }
-
- String toCode() {
- var out = new StringBuffer();
- out.write('\${');
- out.write(dartMessageName);
- out.write('(');
- out.write(mainArgument);
- var args = codeAttributeNames.where((attribute) => this[attribute] != null);
- args.fold(
- out, (buffer, arg) => buffer..write(", $arg: '${this[arg].toCode()}'"));
- out.write(")}");
- return out.toString();
- }
-}
-
-/**
- * Represents a message send of [Intl.gender] inside a message that is to
- * be internationalized. This corresponds to an ICU message syntax "select"
- * with "male", "female", and "other" as the possible options.
- */
-class Gender extends SubMessage {
- Gender();
- /**
- * Create a new Gender providing [mainArgument] and the list of possible
- * clauses. Each clause is expected to be a list whose first element is a
- * variable name and whose second element is either a [String] or
- * a list of strings and [Message] or [VariableSubstitution].
- */
- Gender.from(String mainArgument, List clauses, Message parent)
- : super.from(mainArgument, clauses, parent);
-
- Message female;
- Message male;
- Message other;
-
- String get icuMessageName => "select";
- String get dartMessageName => 'Intl.gender';
-
- get attributeNames => ["female", "male", "other"];
- get codeAttributeNames => attributeNames;
-
- /**
- * The node will have the attribute names as strings, so we translate
- * between those and the fields of the class.
- */
- void operator []=(attributeName, rawValue) {
- var value = Message.from(rawValue, this);
- switch (attributeName) {
- case "female":
- female = value;
- return;
- case "male":
- male = value;
- return;
- case "other":
- other = value;
- return;
- default:
- return;
- }
- }
- Message operator [](String attributeName) {
- switch (attributeName) {
- case "female":
- return female;
- case "male":
- return male;
- case "other":
- return other;
- default:
- return other;
- }
- }
-}
-
-class Plural extends SubMessage {
- Plural();
- Plural.from(String mainArgument, List clauses, Message parent)
- : super.from(mainArgument, clauses, parent);
-
- Message zero;
- Message one;
- Message two;
- Message few;
- Message many;
- Message other;
-
- String get icuMessageName => "plural";
- String get dartMessageName => "Intl.plural";
-
- get attributeNames => ["=0", "=1", "=2", "few", "many", "other"];
- get codeAttributeNames => ["zero", "one", "two", "few", "many", "other"];
-
- /**
- * The node will have the attribute names as strings, so we translate
- * between those and the fields of the class.
- */
- void operator []=(String attributeName, rawValue) {
- var value = Message.from(rawValue, this);
- switch (attributeName) {
- case "zero":
- zero = value;
- return;
- case "=0":
- zero = value;
- return;
- case "one":
- one = value;
- return;
- case "=1":
- one = value;
- return;
- case "two":
- two = value;
- return;
- case "=2":
- two = value;
- return;
- case "few":
- few = value;
- return;
- case "many":
- many = value;
- return;
- case "other":
- other = value;
- return;
- default:
- return;
- }
- }
-
- Message operator [](String attributeName) {
- switch (attributeName) {
- case "zero":
- return zero;
- case "=0":
- return zero;
- case "one":
- return one;
- case "=1":
- return one;
- case "two":
- return two;
- case "=2":
- return two;
- case "few":
- return few;
- case "many":
- return many;
- case "other":
- return other;
- default:
- return other;
- }
- }
-}
-
-/**
- * Represents a message send of [Intl.select] inside a message that is to
- * be internationalized. This corresponds to an ICU message syntax "select"
- * with arbitrary options.
- */
-class Select extends SubMessage {
- Select();
- /**
- * Create a new [Select] providing [mainArgument] and the list of possible
- * clauses. Each clause is expected to be a list whose first element is a
- * variable name and whose second element is either a String or
- * a list of strings and [Message]s or [VariableSubstitution]s.
- */
- Select.from(String mainArgument, List clauses, Message parent)
- : super.from(mainArgument, clauses, parent);
-
- Map<String, Message> cases = new Map<String, Message>();
-
- String get icuMessageName => "select";
- String get dartMessageName => 'Intl.select';
-
- get attributeNames => cases.keys;
- get codeAttributeNames => attributeNames;
-
- void operator []=(attributeName, rawValue) {
- var value = Message.from(rawValue, this);
- cases[attributeName] = value;
- }
-
- Message operator [](String attributeName) {
- var exact = cases[attributeName];
- return exact == null ? cases["other"] : exact;
- }
-
- /**
- * Return the arguments that we care about for the select. In this
- * case they will all be passed in as a Map rather than as the named
- * arguments used in Plural/Gender.
- */
- Map argumentsOfInterestFor(MethodInvocation node) {
- var casesArgument = node.argumentList.arguments[1];
- return new Map.fromIterable(casesArgument.entries,
- key: (node) => node.key.value, value: (node) => node.value);
- }
-
- /**
- * Write out the generated representation of this message. This differs
- * from Plural/Gender in that it prints a literal map rather than
- * named arguments.
- */
- String toCode() {
- var out = new StringBuffer();
- out.write('\${');
- out.write(dartMessageName);
- out.write('(');
- out.write(mainArgument);
- var args = codeAttributeNames;
- out.write(", {");
- args.fold(out,
- (buffer, arg) => buffer..write("'$arg': '${this[arg].toCode()}', "));
- out.write("})}");
- return out.toString();
- }
-}
« no previous file with comments | « packages/intl/lib/src/intl_helpers.dart ('k') | packages/intl/lib/src/lazy_locale_data.dart » ('j') | no next file with comments »

Powered by Google App Engine
This is Rietveld 408576698