Passed
Push — master ( 283033...66ce56 )
by Guangyu
07:51 queued 11s
created

admin/js/angular/angular-message-format.js (3 issues)

1
/**
2
 * @license AngularJS v1.8.2
3
 * (c) 2010-2020 Google LLC. http://angularjs.org
4
 * License: MIT
5
 */
6
(function(window, angular) {'use strict';
7
8
// NOTE: ADVANCED_OPTIMIZATIONS mode.
9
//
10
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
11
// constructs incompatible with that mode.
12
13
/* global isFunction: false */
14
/* global noop: false */
15
/* global toJson: false */
16
/* global $$stringify: false */
17
18
// Convert an index into the string into line/column for use in error messages
19
// As such, this doesn't have to be efficient.
20
function indexToLineAndColumn(text, index) {
21
  var lines = text.split(/\n/g);
22
  for (var i = 0; i < lines.length; i++) {
23
    var line = lines[i];
24
    if (index >= line.length) {
25
      index -= line.length;
26
    } else {
27
      return { line: i + 1, column: index + 1 };
28
    }
29
  }
0 ignored issues
show
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
30
}
31
var PARSE_CACHE_FOR_TEXT_LITERALS = Object.create(null);
32
33
function parseTextLiteral(text) {
34
  var cachedFn = PARSE_CACHE_FOR_TEXT_LITERALS[text];
35
  if (cachedFn != null) {
36
    return cachedFn;
37
  }
38
  function parsedFn(context) { return text; }
39
  parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
40
    var unwatch = scope['$watch'](noop,
41
        function textLiteralWatcher() {
42
          listener(text, text, scope);
43
          unwatch();
44
        },
45
        objectEquality);
46
    return unwatch;
47
  };
48
  PARSE_CACHE_FOR_TEXT_LITERALS[text] = parsedFn;
49
  parsedFn['exp'] = text; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
50
  parsedFn['expressions'] = []; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
51
  return parsedFn;
52
}
53
54
function subtractOffset(expressionFn, offset) {
55
  if (offset === 0) {
56
    return expressionFn;
57
  }
58
  function minusOffset(value) {
59
    return (value == null) ? value : value - offset;
60
  }
61
  function parsedFn(context) { return minusOffset(expressionFn(context)); }
62
  var unwatch;
63
  parsedFn['$$watchDelegate'] = function watchDelegate(scope, listener, objectEquality) {
64
    unwatch = scope['$watch'](expressionFn,
65
        function pluralExpressionWatchListener(newValue, oldValue) {
66
          listener(minusOffset(newValue), minusOffset(oldValue), scope);
67
        },
68
        objectEquality);
69
    return unwatch;
70
  };
71
  return parsedFn;
72
}
73
74
// NOTE: ADVANCED_OPTIMIZATIONS mode.
75
//
76
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
77
// constructs incompatible with that mode.
78
79
/* global $interpolateMinErr: false */
80
/* global isFunction: false */
81
/* global noop: false */
82
83
/**
84
 * @constructor
85
 * @private
86
 */
87
function MessageSelectorBase(expressionFn, choices) {
88
  var self = this;
89
  this.expressionFn = expressionFn;
90
  this.choices = choices;
91
  if (choices['other'] === undefined) {
92
    throw $interpolateMinErr('reqother', '“other” is a required option.');
93
  }
94
  this.parsedFn = function(context) { return self.getResult(context); };
95
  this.parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
96
    return self.watchDelegate(scope, listener, objectEquality);
97
  };
98
  this.parsedFn['exp'] = expressionFn['exp'];
99
  this.parsedFn['expressions'] = expressionFn['expressions'];
100
}
101
102
MessageSelectorBase.prototype.getMessageFn = function getMessageFn(value) {
103
  return this.choices[this.categorizeValue(value)];
104
};
105
106
MessageSelectorBase.prototype.getResult = function getResult(context) {
107
  return this.getMessageFn(this.expressionFn(context))(context);
108
};
109
110
MessageSelectorBase.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
111
  var watchers = new MessageSelectorWatchers(this, scope, listener, objectEquality);
112
  return function() { watchers.cancelWatch(); };
113
};
114
115
/**
116
 * @constructor
117
 * @private
118
 */
119
function MessageSelectorWatchers(msgSelector, scope, listener, objectEquality) {
120
  var self = this;
121
  this.scope = scope;
122
  this.msgSelector = msgSelector;
123
  this.listener = listener;
124
  this.objectEquality = objectEquality;
125
  this.lastMessage = undefined;
126
  this.messageFnWatcher = noop;
127
  var expressionFnListener = function(newValue, oldValue) { return self.expressionFnListener(newValue, oldValue); };
128
  this.expressionFnWatcher = scope['$watch'](msgSelector.expressionFn, expressionFnListener, objectEquality);
129
}
130
131
MessageSelectorWatchers.prototype.expressionFnListener = function expressionFnListener(newValue, oldValue) {
132
  var self = this;
133
  this.messageFnWatcher();
134
  var messageFnListener = function(newMessage, oldMessage) { return self.messageFnListener(newMessage, oldMessage); };
135
  var messageFn = this.msgSelector.getMessageFn(newValue);
136
  this.messageFnWatcher = this.scope['$watch'](messageFn, messageFnListener, this.objectEquality);
137
};
138
139
MessageSelectorWatchers.prototype.messageFnListener = function messageFnListener(newMessage, oldMessage) {
140
  this.listener.call(null, newMessage, newMessage === oldMessage ? newMessage : this.lastMessage, this.scope);
141
  this.lastMessage = newMessage;
142
};
143
144
MessageSelectorWatchers.prototype.cancelWatch = function cancelWatch() {
145
  this.expressionFnWatcher();
146
  this.messageFnWatcher();
147
};
148
149
/**
150
 * @constructor
151
 * @extends MessageSelectorBase
152
 * @private
153
 */
154
function SelectMessage(expressionFn, choices) {
155
  MessageSelectorBase.call(this, expressionFn, choices);
156
}
157
158
function SelectMessageProto() {}
159
SelectMessageProto.prototype = MessageSelectorBase.prototype;
160
161
SelectMessage.prototype = new SelectMessageProto();
162
SelectMessage.prototype.categorizeValue = function categorizeSelectValue(value) {
163
  return (this.choices[value] !== undefined) ? value : 'other';
164
};
165
166
/**
167
 * @constructor
168
 * @extends MessageSelectorBase
169
 * @private
170
 */
171
function PluralMessage(expressionFn, choices, offset, pluralCat) {
172
  MessageSelectorBase.call(this, expressionFn, choices);
173
  this.offset = offset;
174
  this.pluralCat = pluralCat;
175
}
176
177
function PluralMessageProto() {}
178
PluralMessageProto.prototype = MessageSelectorBase.prototype;
179
180
PluralMessage.prototype = new PluralMessageProto();
181
PluralMessage.prototype.categorizeValue = function categorizePluralValue(value) {
182
  if (isNaN(value)) {
183
    return 'other';
184
  } else if (this.choices[value] !== undefined) {
185
    return value;
186
  } else {
187
    var category = this.pluralCat(value - this.offset);
188
    return (this.choices[category] !== undefined) ? category : 'other';
189
  }
190
};
191
192
// NOTE: ADVANCED_OPTIMIZATIONS mode.
193
//
194
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
195
// constructs incompatible with that mode.
196
197
/* global $interpolateMinErr: false */
198
/* global isFunction: false */
199
/* global parseTextLiteral: false */
200
201
/**
202
 * @constructor
203
 * @private
204
 */
205
function InterpolationParts(trustedContext, allOrNothing) {
206
  this.trustedContext = trustedContext;
207
  this.allOrNothing = allOrNothing;
208
  this.textParts = [];
209
  this.expressionFns = [];
210
  this.expressionIndices = [];
211
  this.partialText = '';
212
  this.concatParts = null;
213
}
214
215
InterpolationParts.prototype.flushPartialText = function flushPartialText() {
216
  if (this.partialText) {
217
    if (this.concatParts == null) {
218
      this.textParts.push(this.partialText);
219
    } else {
220
      this.textParts.push(this.concatParts.join(''));
221
      this.concatParts = null;
222
    }
223
    this.partialText = '';
224
  }
225
};
226
227
InterpolationParts.prototype.addText = function addText(text) {
228
  if (text.length) {
229
    if (!this.partialText) {
230
      this.partialText = text;
231
    } else if (this.concatParts) {
232
      this.concatParts.push(text);
233
    } else {
234
      this.concatParts = [this.partialText, text];
235
    }
236
  }
237
};
238
239
InterpolationParts.prototype.addExpressionFn = function addExpressionFn(expressionFn) {
240
  this.flushPartialText();
241
  this.expressionIndices.push(this.textParts.length);
242
  this.expressionFns.push(expressionFn);
243
  this.textParts.push('');
244
};
245
246
InterpolationParts.prototype.getExpressionValues = function getExpressionValues(context) {
247
  var expressionValues = new Array(this.expressionFns.length);
248
  for (var i = 0; i < this.expressionFns.length; i++) {
249
    expressionValues[i] = this.expressionFns[i](context);
250
  }
251
  return expressionValues;
252
};
253
254
InterpolationParts.prototype.getResult = function getResult(expressionValues) {
255
  for (var i = 0; i < this.expressionIndices.length; i++) {
256
    var expressionValue = expressionValues[i];
257
    if (this.allOrNothing && expressionValue === undefined) return;
0 ignored issues
show
Comprehensibility Best Practice introduced by
Are you sure this return statement is not missing an argument? If this is intended, consider adding an explicit undefined like return undefined;.
Loading history...
258
    this.textParts[this.expressionIndices[i]] = expressionValue;
259
  }
260
  return this.textParts.join('');
261
};
262
263
264
InterpolationParts.prototype.toParsedFn = function toParsedFn(mustHaveExpression, originalText) {
265
  var self = this;
266
  this.flushPartialText();
267
  if (mustHaveExpression && this.expressionFns.length === 0) {
268
    return undefined;
269
  }
270
  if (this.textParts.length === 0) {
271
    return parseTextLiteral('');
272
  }
273
  if (this.trustedContext && this.textParts.length > 1) {
274
    $interpolateMinErr['throwNoconcat'](originalText);
275
  }
276
  if (this.expressionFns.length === 0) {
277
    if (this.textParts.length !== 1) { this.errorInParseLogic(); }
278
    return parseTextLiteral(this.textParts[0]);
279
  }
280
  var parsedFn = function(context) {
281
    return self.getResult(self.getExpressionValues(context));
282
  };
283
  parsedFn['$$watchDelegate'] = function $$watchDelegate(scope, listener, objectEquality) {
284
    return self.watchDelegate(scope, listener, objectEquality);
285
  };
286
287
  parsedFn['exp'] = originalText; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
288
  parsedFn['expressions'] = new Array(this.expressionFns.length); // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
289
  for (var i = 0; i < this.expressionFns.length; i++) {
290
    parsedFn['expressions'][i] = this.expressionFns[i]['exp'];
291
  }
292
293
  return parsedFn;
294
};
295
296
InterpolationParts.prototype.watchDelegate = function watchDelegate(scope, listener, objectEquality) {
297
  var watcher = new InterpolationPartsWatcher(this, scope, listener, objectEquality);
298
  return function() { watcher.cancelWatch(); };
299
};
300
301
function InterpolationPartsWatcher(interpolationParts, scope, listener, objectEquality) {
302
  this.interpolationParts = interpolationParts;
303
  this.scope = scope;
304
  this.previousResult = (undefined);
305
  this.listener = listener;
306
  var self = this;
307
  this.expressionFnsWatcher = scope['$watchGroup'](interpolationParts.expressionFns, function(newExpressionValues, oldExpressionValues) {
308
    self.watchListener(newExpressionValues, oldExpressionValues);
309
  });
310
}
311
312
InterpolationPartsWatcher.prototype.watchListener = function watchListener(newExpressionValues, oldExpressionValues) {
313
  var result = this.interpolationParts.getResult(newExpressionValues);
314
  this.listener.call(null, result, newExpressionValues === oldExpressionValues ? result : this.previousResult, this.scope);
315
  this.previousResult = result;
316
};
317
318
InterpolationPartsWatcher.prototype.cancelWatch = function cancelWatch() {
319
  this.expressionFnsWatcher();
320
};
321
322
// NOTE: ADVANCED_OPTIMIZATIONS mode.
323
//
324
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
325
// constructs incompatible with that mode.
326
327
/* global $interpolateMinErr: false */
328
/* global indexToLineAndColumn: false */
329
/* global InterpolationParts: false */
330
/* global PluralMessage: false */
331
/* global SelectMessage: false */
332
/* global subtractOffset: false */
333
334
// The params src and dst are exactly one of two types: NestedParserState or MessageFormatParser.
335
// This function is fully optimized by V8. (inspect via IRHydra or --trace-deopt.)
336
// The idea behind writing it this way is to avoid repeating oneself.  This is the ONE place where
337
// the parser state that is saved/restored when parsing nested mustaches is specified.
338
function copyNestedParserState(src, dst) {
339
  dst.expressionFn = src.expressionFn;
340
  dst.expressionMinusOffsetFn = src.expressionMinusOffsetFn;
341
  dst.pluralOffset = src.pluralOffset;
342
  dst.choices = src.choices;
343
  dst.choiceKey = src.choiceKey;
344
  dst.interpolationParts = src.interpolationParts;
345
  dst.ruleChoiceKeyword = src.ruleChoiceKeyword;
346
  dst.msgStartIndex = src.msgStartIndex;
347
  dst.expressionStartIndex = src.expressionStartIndex;
348
}
349
350
function NestedParserState(parser) {
351
  copyNestedParserState(parser, this);
352
}
353
354
/**
355
 * @constructor
356
 * @private
357
 */
358
function MessageFormatParser(text, startIndex, $parse, pluralCat, stringifier,
359
                             mustHaveExpression, trustedContext, allOrNothing) {
360
  this.text = text;
361
  this.index = startIndex || 0;
362
  this.$parse = $parse;
363
  this.pluralCat = pluralCat;
364
  this.stringifier = stringifier;
365
  this.mustHaveExpression = !!mustHaveExpression;
366
  this.trustedContext = trustedContext;
367
  this.allOrNothing = !!allOrNothing;
368
  this.expressionFn = null;
369
  this.expressionMinusOffsetFn = null;
370
  this.pluralOffset = null;
371
  this.choices = null;
372
  this.choiceKey = null;
373
  this.interpolationParts = null;
374
  this.msgStartIndex = null;
375
  this.nestedStateStack = [];
376
  this.parsedFn = null;
377
  this.rule = null;
378
  this.ruleStack = null;
379
  this.ruleChoiceKeyword = null;
380
  this.interpNestLevel = null;
381
  this.expressionStartIndex = null;
382
  this.stringStartIndex = null;
383
  this.stringQuote = null;
384
  this.stringInterestsRe = null;
385
  this.angularOperatorStack = null;
386
  this.textPart = null;
387
}
388
389
// preserve v8 optimization.
390
var EMPTY_STATE = new NestedParserState(new MessageFormatParser(
391
        /* text= */ '', /* startIndex= */ 0, /* $parse= */ null, /* pluralCat= */ null, /* stringifier= */ null,
392
        /* mustHaveExpression= */ false, /* trustedContext= */ null, /* allOrNothing */ false));
393
394
MessageFormatParser.prototype.pushState = function pushState() {
395
  this.nestedStateStack.push(new NestedParserState(this));
396
  copyNestedParserState(EMPTY_STATE, this);
397
};
398
399
MessageFormatParser.prototype.popState = function popState() {
400
  if (this.nestedStateStack.length === 0) {
401
    this.errorInParseLogic();
402
  }
403
  var previousState = this.nestedStateStack.pop();
404
  copyNestedParserState(previousState, this);
405
};
406
407
// Oh my JavaScript!  Who knew you couldn't match a regex at a specific
408
// location in a string but will always search forward?!
409
// Apparently you'll be growing this ability via the sticky flag (y) in
410
// ES6.  I'll just to work around you for now.
411
MessageFormatParser.prototype.matchRe = function matchRe(re, search) {
412
  re.lastIndex = this.index;
413
  var match = re.exec(this.text);
414
  if (match != null && (search === true || (match.index === this.index))) {
415
    this.index = re.lastIndex;
416
    return match;
417
  }
418
  return null;
419
};
420
421
MessageFormatParser.prototype.searchRe = function searchRe(re) {
422
  return this.matchRe(re, true);
423
};
424
425
426
MessageFormatParser.prototype.consumeRe = function consumeRe(re) {
427
  // Without the sticky flag, we can't use the .test() method to consume a
428
  // match at the current index.  Instead, we'll use the slower .exec() method
429
  // and verify match.index.
430
  return !!this.matchRe(re);
431
};
432
433
// Run through our grammar avoiding deeply nested function call chains.
434
MessageFormatParser.prototype.run = function run(initialRule) {
435
  this.ruleStack = [initialRule];
436
  do {
437
    this.rule = this.ruleStack.pop();
438
    while (this.rule) {
439
      this.rule();
440
    }
441
    this.assertRuleOrNull(this.rule);
442
  } while (this.ruleStack.length > 0);
443
};
444
445
MessageFormatParser.prototype.errorInParseLogic = function errorInParseLogic() {
446
    throw $interpolateMinErr('logicbug',
447
        'The messageformat parser has encountered an internal error.  Please file a github issue against the AngularJS project and provide this message text that triggers the bug.  Text: “{0}”',
448
        this.text);
449
};
450
451
MessageFormatParser.prototype.assertRuleOrNull = function assertRuleOrNull(rule) {
452
  if (rule === undefined) {
453
    this.errorInParseLogic();
454
  }
455
};
456
457
var NEXT_WORD_RE = /\s*(\w+)\s*/g;
458
MessageFormatParser.prototype.errorExpecting = function errorExpecting() {
459
  // What was wrong with the syntax? Unsupported type, missing comma, or something else?
460
  var match = this.matchRe(NEXT_WORD_RE), position;
461
  if (match == null) {
462
    position = indexToLineAndColumn(this.text, this.index);
463
    throw $interpolateMinErr('reqarg',
464
        'Expected one of “plural” or “select” at line {0}, column {1} of text “{2}”',
465
        position.line, position.column, this.text);
466
  }
467
  var word = match[1];
468
  if (word === 'select' || word === 'plural') {
469
    position = indexToLineAndColumn(this.text, this.index);
470
    throw $interpolateMinErr('reqcomma',
471
        'Expected a comma after the keyword “{0}” at line {1}, column {2} of text “{3}”',
472
        word, position.line, position.column, this.text);
473
  } else {
474
    position = indexToLineAndColumn(this.text, this.index);
475
    throw $interpolateMinErr('unknarg',
476
        'Unsupported keyword “{0}” at line {0}, column {1}. Only “plural” and “select” are currently supported.  Text: “{3}”',
477
        word, position.line, position.column, this.text);
478
  }
479
};
480
481
var STRING_START_RE = /['"]/g;
482
MessageFormatParser.prototype.ruleString = function ruleString() {
483
  var match = this.matchRe(STRING_START_RE);
484
  if (match == null) {
485
    var position = indexToLineAndColumn(this.text, this.index);
486
    throw $interpolateMinErr('wantstring',
487
        'Expected the beginning of a string at line {0}, column {1} in text “{2}”',
488
        position.line, position.column, this.text);
489
  }
490
  this.startStringAtMatch(match);
491
};
492
493
MessageFormatParser.prototype.startStringAtMatch = function startStringAtMatch(match) {
494
  this.stringStartIndex = match.index;
495
  this.stringQuote = match[0];
496
  this.stringInterestsRe = this.stringQuote === '\'' ? SQUOTED_STRING_INTEREST_RE : DQUOTED_STRING_INTEREST_RE;
497
  this.rule = this.ruleInsideString;
498
};
499
500
var SQUOTED_STRING_INTEREST_RE = /\\(?:\\|'|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{2}|[0-7]{3}|\r\n|\n|[\s\S])|'/g;
501
var DQUOTED_STRING_INTEREST_RE = /\\(?:\\|"|u[0-9A-Fa-f]{4}|x[0-9A-Fa-f]{2}|[0-7]{3}|\r\n|\n|[\s\S])|"/g;
502
MessageFormatParser.prototype.ruleInsideString = function ruleInsideString() {
503
  var match = this.searchRe(this.stringInterestsRe);
504
  if (match == null) {
505
    var position = indexToLineAndColumn(this.text, this.stringStartIndex);
506
    throw $interpolateMinErr('untermstr',
507
        'The string beginning at line {0}, column {1} is unterminated in text “{2}”',
508
        position.line, position.column, this.text);
509
  }
510
  if (match[0] === this.stringQuote) {
511
    this.rule = null;
512
  }
513
};
514
515
var PLURAL_OR_SELECT_ARG_TYPE_RE = /\s*(plural|select)\s*,\s*/g;
516
MessageFormatParser.prototype.rulePluralOrSelect = function rulePluralOrSelect() {
517
  var match = this.searchRe(PLURAL_OR_SELECT_ARG_TYPE_RE);
518
  if (match == null) {
519
    this.errorExpecting();
520
  }
521
  var argType = match[1];
522
  switch (argType) {
523
    case 'plural': this.rule = this.rulePluralStyle; break;
524
    case 'select': this.rule = this.ruleSelectStyle; break;
525
    default: this.errorInParseLogic();
526
  }
527
};
528
529
MessageFormatParser.prototype.rulePluralStyle = function rulePluralStyle() {
530
  this.choices = Object.create(null);
531
  this.ruleChoiceKeyword = this.rulePluralValueOrKeyword;
532
  this.rule = this.rulePluralOffset;
533
};
534
535
MessageFormatParser.prototype.ruleSelectStyle = function ruleSelectStyle() {
536
  this.choices = Object.create(null);
537
  this.ruleChoiceKeyword = this.ruleSelectKeyword;
538
  this.rule = this.ruleSelectKeyword;
539
};
540
541
var NUMBER_RE = /[0]|(?:[1-9][0-9]*)/g;
542
var PLURAL_OFFSET_RE = new RegExp('\\s*offset\\s*:\\s*(' + NUMBER_RE.source + ')', 'g');
543
544
MessageFormatParser.prototype.rulePluralOffset = function rulePluralOffset() {
545
  var match = this.matchRe(PLURAL_OFFSET_RE);
546
  this.pluralOffset = (match == null) ? 0 : parseInt(match[1], 10);
547
  this.expressionMinusOffsetFn = subtractOffset(this.expressionFn, this.pluralOffset);
548
  this.rule = this.rulePluralValueOrKeyword;
549
};
550
551
MessageFormatParser.prototype.assertChoiceKeyIsNew = function assertChoiceKeyIsNew(choiceKey, index) {
552
  if (this.choices[choiceKey] !== undefined) {
553
    var position = indexToLineAndColumn(this.text, index);
554
    throw $interpolateMinErr('dupvalue',
555
        'The choice “{0}” is specified more than once. Duplicate key is at line {1}, column {2} in text “{3}”',
556
        choiceKey, position.line, position.column, this.text);
557
  }
558
};
559
560
var SELECT_KEYWORD = /\s*(\w+)/g;
561
MessageFormatParser.prototype.ruleSelectKeyword = function ruleSelectKeyword() {
562
  var match = this.matchRe(SELECT_KEYWORD);
563
  if (match == null) {
564
    this.parsedFn = new SelectMessage(this.expressionFn, this.choices).parsedFn;
565
    this.rule = null;
566
    return;
567
  }
568
  this.choiceKey = match[1];
569
  this.assertChoiceKeyIsNew(this.choiceKey, match.index);
570
  this.rule = this.ruleMessageText;
571
};
572
573
var EXPLICIT_VALUE_OR_KEYWORD_RE = new RegExp('\\s*(?:(?:=(' + NUMBER_RE.source + '))|(\\w+))', 'g');
574
MessageFormatParser.prototype.rulePluralValueOrKeyword = function rulePluralValueOrKeyword() {
575
  var match = this.matchRe(EXPLICIT_VALUE_OR_KEYWORD_RE);
576
  if (match == null) {
577
    this.parsedFn = new PluralMessage(this.expressionFn, this.choices, this.pluralOffset, this.pluralCat).parsedFn;
578
    this.rule = null;
579
    return;
580
  }
581
  if (match[1] != null) {
582
    this.choiceKey = parseInt(match[1], 10);
583
  } else {
584
    this.choiceKey = match[2];
585
  }
586
  this.assertChoiceKeyIsNew(this.choiceKey, match.index);
587
  this.rule = this.ruleMessageText;
588
};
589
590
var BRACE_OPEN_RE = /\s*\{/g;
591
var BRACE_CLOSE_RE = /}/g;
592
MessageFormatParser.prototype.ruleMessageText = function ruleMessageText() {
593
  if (!this.consumeRe(BRACE_OPEN_RE)) {
594
    var position = indexToLineAndColumn(this.text, this.index);
595
    throw $interpolateMinErr('reqopenbrace',
596
        'The plural choice “{0}” must be followed by a message in braces at line {1}, column {2} in text “{3}”',
597
        this.choiceKey, position.line, position.column, this.text);
598
  }
599
  this.msgStartIndex = this.index;
600
  this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
601
  this.rule = this.ruleInInterpolationOrMessageText;
602
};
603
604
// Note: Since "\" is used as an escape character, don't allow it to be part of the
605
// startSymbol/endSymbol when I add the feature to allow them to be redefined.
606
var INTERP_OR_END_MESSAGE_RE = /\\.|{{|}/g;
607
var INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE = /\\.|{{|#|}/g;
608
var ESCAPE_OR_MUSTACHE_BEGIN_RE = /\\.|{{/g;
609
MessageFormatParser.prototype.advanceInInterpolationOrMessageText = function advanceInInterpolationOrMessageText() {
610
  var currentIndex = this.index, match;
611
  if (this.ruleChoiceKeyword == null) { // interpolation
612
    match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
613
    if (match == null) { // End of interpolation text.  Nothing more to process.
614
      this.textPart = this.text.substring(currentIndex);
615
      this.index = this.text.length;
616
      return null;
617
    }
618
  } else {
619
    match = this.searchRe(this.ruleChoiceKeyword === this.rulePluralValueOrKeyword ?
620
                          INTERP_OR_PLURALVALUE_OR_END_MESSAGE_RE : INTERP_OR_END_MESSAGE_RE);
621
    if (match == null) {
622
      var position = indexToLineAndColumn(this.text, this.msgStartIndex);
623
      throw $interpolateMinErr('reqendbrace',
624
          'The plural/select choice “{0}” message starting at line {1}, column {2} does not have an ending closing brace. Text “{3}”',
625
          this.choiceKey, position.line, position.column, this.text);
626
    }
627
  }
628
  // match is non-null.
629
  var token = match[0];
630
  this.textPart = this.text.substring(currentIndex, match.index);
631
  return token;
632
};
633
634
MessageFormatParser.prototype.ruleInInterpolationOrMessageText = function ruleInInterpolationOrMessageText() {
635
  var currentIndex = this.index;
636
  var token = this.advanceInInterpolationOrMessageText();
637
  if (token == null) {
638
    // End of interpolation text.  Nothing more to process.
639
    this.index = this.text.length;
640
    this.interpolationParts.addText(this.text.substring(currentIndex));
641
    this.rule = null;
642
    return;
643
  }
644
  if (token[0] === '\\') {
645
    // unescape next character and continue
646
    this.interpolationParts.addText(this.textPart + token[1]);
647
    return;
648
  }
649
  this.interpolationParts.addText(this.textPart);
650
  if (token === '{{') {
651
    this.pushState();
652
    this.ruleStack.push(this.ruleEndMustacheInInterpolationOrMessage);
653
    this.rule = this.ruleEnteredMustache;
654
  } else if (token === '}') {
655
    this.choices[this.choiceKey] = this.interpolationParts.toParsedFn(/*mustHaveExpression=*/false, this.text);
656
    this.rule = this.ruleChoiceKeyword;
657
  } else if (token === '#') {
658
    this.interpolationParts.addExpressionFn(this.expressionMinusOffsetFn);
659
  } else {
660
    this.errorInParseLogic();
661
  }
662
};
663
664
MessageFormatParser.prototype.ruleInterpolate = function ruleInterpolate() {
665
  this.interpolationParts = new InterpolationParts(this.trustedContext, this.allOrNothing);
666
  this.rule = this.ruleInInterpolation;
667
};
668
669
MessageFormatParser.prototype.ruleInInterpolation = function ruleInInterpolation() {
670
  var currentIndex = this.index;
671
  var match = this.searchRe(ESCAPE_OR_MUSTACHE_BEGIN_RE);
672
  if (match == null) {
673
    // End of interpolation text.  Nothing more to process.
674
    this.index = this.text.length;
675
    this.interpolationParts.addText(this.text.substring(currentIndex));
676
    this.parsedFn = this.interpolationParts.toParsedFn(this.mustHaveExpression, this.text);
677
    this.rule = null;
678
    return;
679
  }
680
  var token = match[0];
681
  if (token[0] === '\\') {
682
    // unescape next character and continue
683
    this.interpolationParts.addText(this.text.substring(currentIndex, match.index) + token[1]);
684
    return;
685
  }
686
  this.interpolationParts.addText(this.text.substring(currentIndex, match.index));
687
  this.pushState();
688
  this.ruleStack.push(this.ruleInterpolationEndMustache);
689
  this.rule = this.ruleEnteredMustache;
690
};
691
692
MessageFormatParser.prototype.ruleInterpolationEndMustache = function ruleInterpolationEndMustache() {
693
  var expressionFn = this.parsedFn;
694
  this.popState();
695
  this.interpolationParts.addExpressionFn(expressionFn);
696
  this.rule = this.ruleInInterpolation;
697
};
698
699
MessageFormatParser.prototype.ruleEnteredMustache = function ruleEnteredMustache() {
700
  this.parsedFn = null;
701
  this.ruleStack.push(this.ruleEndMustache);
702
  this.rule = this.ruleAngularExpression;
703
};
704
705
MessageFormatParser.prototype.ruleEndMustacheInInterpolationOrMessage = function ruleEndMustacheInInterpolationOrMessage() {
706
  var expressionFn = this.parsedFn;
707
  this.popState();
708
  this.interpolationParts.addExpressionFn(expressionFn);
709
  this.rule = this.ruleInInterpolationOrMessageText;
710
};
711
712
713
714
var INTERP_END_RE = /\s*}}/g;
715
MessageFormatParser.prototype.ruleEndMustache = function ruleEndMustache() {
716
  var match = this.matchRe(INTERP_END_RE);
717
  if (match == null) {
718
    var position = indexToLineAndColumn(this.text, this.index);
719
    throw $interpolateMinErr('reqendinterp',
720
        'Expecting end of interpolation symbol, “{0}”, at line {1}, column {2} in text “{3}”',
721
        '}}', position.line, position.column, this.text);
722
  }
723
  if (this.parsedFn == null) {
724
    // If we parsed a MessageFormat extension, (e.g. select/plural today, maybe more some other
725
    // day), then the result *has* to be a string and those rules would have already set
726
    // this.parsedFn.  If there was no MessageFormat extension, then there is no requirement to
727
    // stringify the result and parsedFn isn't set.  We set it here.  While we could have set it
728
    // unconditionally when exiting the AngularJS expression, I intend for us to not just replace
729
    // $interpolate, but also to replace $parse in a future version (so ng-bind can work), and in
730
    // such a case we do not want to unnecessarily stringify something if it's not going to be used
731
    // in a string context.
732
    this.parsedFn = this.$parse(this.expressionFn, this.stringifier);
733
    this.parsedFn['exp'] = this.expressionFn['exp']; // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
734
    this.parsedFn['expressions'] = this.expressionFn['expressions']; // Require this to call $compile.$$addBindingInfo() which allows Protractor to find elements by binding.
735
  }
736
  this.rule = null;
737
};
738
739
MessageFormatParser.prototype.ruleAngularExpression = function ruleAngularExpression() {
740
  this.angularOperatorStack = [];
741
  this.expressionStartIndex = this.index;
742
  this.rule = this.ruleInAngularExpression;
743
};
744
745
function getEndOperator(opBegin) {
746
  switch (opBegin) {
747
    case '{': return '}';
748
    case '[': return ']';
749
    case '(': return ')';
750
    default: return null;
751
  }
752
}
753
754
function getBeginOperator(opEnd) {
755
  switch (opEnd) {
756
    case '}': return '{';
757
    case ']': return '[';
758
    case ')': return '(';
759
    default: return null;
760
  }
761
}
762
763
// TODO(chirayu): The interpolation endSymbol must also be accounted for. It
764
// just so happens that "}" is an operator so it's in the list below.  But we
765
// should support any other type of start/end interpolation symbol.
766
var INTERESTING_OPERATORS_RE = /[[\]{}()'",]/g;
767
MessageFormatParser.prototype.ruleInAngularExpression = function ruleInAngularExpression() {
768
  var match = this.searchRe(INTERESTING_OPERATORS_RE);
769
  var position;
770
  if (match == null) {
771
    if (this.angularOperatorStack.length === 0) {
772
      // This is the end of the AngularJS expression so this is actually a
773
      // success.  Note that when inside an interpolation, this means we even
774
      // consumed the closing interpolation symbols if they were curlies.  This
775
      // is NOT an error at this point but will become an error further up the
776
      // stack when the part that saw the opening curlies is unable to find the
777
      // closing ones.
778
      this.index = this.text.length;
779
      this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
780
      // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
781
      this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
782
      this.expressionFn['expressions'] = this.expressionFn['expressions'];
783
      this.rule = null;
784
      return;
785
    }
786
    var innermostOperator = this.angularOperatorStack[0];
787
    throw $interpolateMinErr('badexpr',
788
        'Unexpected end of AngularJS expression.  Expecting operator “{0}” at the end of the text “{1}”',
789
        this.getEndOperator(innermostOperator), this.text);
790
  }
791
  var operator = match[0];
792
  if (operator === '\'' || operator === '"') {
793
    this.ruleStack.push(this.ruleInAngularExpression);
794
    this.startStringAtMatch(match);
795
    return;
796
  }
797
  if (operator === ',') {
798
    if (this.trustedContext) {
799
      position = indexToLineAndColumn(this.text, this.index);
800
      throw $interpolateMinErr('unsafe',
801
          'Use of select/plural MessageFormat syntax is currently disallowed in a secure context ({0}).  At line {1}, column {2} of text “{3}”',
802
          this.trustedContext, position.line, position.column, this.text);
803
    }
804
    // only the top level comma has relevance.
805
    if (this.angularOperatorStack.length === 0) {
806
      // todo: does this need to be trimmed?
807
      this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, match.index));
808
      // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
809
      this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, match.index);
810
      this.expressionFn['expressions'] = this.expressionFn['expressions'];
811
      this.rule = null;
812
      this.rule = this.rulePluralOrSelect;
813
    }
814
    return;
815
  }
816
  if (getEndOperator(operator) != null) {
817
    this.angularOperatorStack.unshift(operator);
818
    return;
819
  }
820
  var beginOperator = getBeginOperator(operator);
821
  if (beginOperator == null) {
822
    this.errorInParseLogic();
823
  }
824
  if (this.angularOperatorStack.length > 0) {
825
    if (beginOperator === this.angularOperatorStack[0]) {
826
      this.angularOperatorStack.shift();
827
      return;
828
    }
829
    position = indexToLineAndColumn(this.text, this.index);
830
    throw $interpolateMinErr('badexpr',
831
        'Unexpected operator “{0}” at line {1}, column {2} in text. Was expecting “{3}”. Text: “{4}”',
832
        operator, position.line, position.column, getEndOperator(this.angularOperatorStack[0]), this.text);
833
  }
834
  // We are trying to pop off the operator stack but there really isn't anything to pop off.
835
  this.index = match.index;
836
  this.expressionFn = this.$parse(this.text.substring(this.expressionStartIndex, this.index));
837
  // Needed to pretend to be $interpolate for tests copied from interpolateSpec.js
838
  this.expressionFn['exp'] = this.text.substring(this.expressionStartIndex, this.index);
839
  this.expressionFn['expressions'] = this.expressionFn['expressions'];
840
  this.rule = null;
841
};
842
843
// NOTE: ADVANCED_OPTIMIZATIONS mode.
844
//
845
// This file is compiled with Closure compiler's ADVANCED_OPTIMIZATIONS flag! Be wary of using
846
// constructs incompatible with that mode.
847
848
/* global $interpolateMinErr: true */
849
/* global isFunction: true */
850
/* global noop: true */
851
/* global toJson: true */
852
/* global MessageFormatParser: false */
853
854
/**
855
 * @ngdoc module
856
 * @name ngMessageFormat
857
 * @packageName angular-message-format
858
 *
859
 * @description
860
 *
861
 * ## What is  ngMessageFormat?
862
 *
863
 * The ngMessageFormat module extends the AngularJS {@link ng.$interpolate `$interpolate`} service
864
 * with a syntax for handling pluralization and gender specific messages, which is based on the
865
 * [ICU MessageFormat syntax][ICU].
866
 *
867
 * See [the design doc][ngMessageFormat doc] for more information.
868
 *
869
 * [ICU]: http://userguide.icu-project.org/formatparse/messages#TOC-MessageFormat
870
 * [ngMessageFormat doc]: https://docs.google.com/a/google.com/document/d/1pbtW2yvtmFBikfRrJd8VAsabiFkKezmYZ_PbgdjQOVU/edit
871
 *
872
 * ## Examples
873
 *
874
 * ### Gender
875
 *
876
 * This example uses the "select" keyword to specify the message based on gender.
877
 *
878
 * <example name="ngMessageFormat-example-gender" module="msgFmtExample" deps="angular-message-format.js">
879
 * <file name="index.html">
880
 *  <div ng-controller="AppController">
881
 *    Select Recipient:<br>
882
      <select ng-model="recipient" ng-options="person as person.name for person in recipients">
883
      </select>
884
      <p>{{recipient.gender, select,
885
                male {{{recipient.name}} unwrapped his gift. }
886
                female {{{recipient.name}} unwrapped her gift. }
887
                other {{{recipient.name}} unwrapped their gift. }
888
      }}</p>
889
 *  </div>
890
 * </file>
891
 * <file name="script.js">
892
 *   function Person(name, gender) {
893
 *     this.name = name;
894
 *     this.gender = gender;
895
 *   }
896
 *
897
 *   var alice   = new Person('Alice', 'female'),
898
 *       bob     = new Person('Bob', 'male'),
899
 *       ashley = new Person('Ashley', '');
900
 *
901
 *   angular.module('msgFmtExample', ['ngMessageFormat'])
902
 *     .controller('AppController', ['$scope', function($scope) {
903
 *         $scope.recipients = [alice, bob, ashley];
904
 *         $scope.recipient = $scope.recipients[0];
905
 *       }]);
906
 * </file>
907
 * </example>
908
 *
909
 * ### Plural
910
 *
911
 * This example shows how the "plural" keyword is used to account for a variable number of entities.
912
 * The "#" variable holds the current number and can be embedded in the message.
913
 *
914
 * Note that "=1" takes precedence over "one".
915
 *
916
 * The example also shows the "offset" keyword, which allows you to offset the value of the "#" variable.
917
 *
918
 * <example name="ngMessageFormat-example-plural" module="msgFmtExample" deps="angular-message-format.js">
919
 * <file name="index.html">
920
 *   <div ng-controller="AppController">
921
 *    <button ng-click="recipients.pop()" id="decreaseRecipients">decreaseRecipients</button><br>
922
 *    Select recipients:<br>
923
 *    <select multiple size=5 ng-model="recipients" ng-options="person as person.name for person in people">
924
 *    </select><br>
925
 *     <p>{{recipients.length, plural, offset:1
926
 *             =0    {{{sender.name}} gave no gifts (\#=#)}
927
 *             =1    {{{sender.name}} gave a gift to {{recipients[0].name}} (\#=#)}
928
 *             one   {{{sender.name}} gave {{recipients[0].name}} and one other person a gift (\#=#)}
929
 *             other {{{sender.name}} gave {{recipients[0].name}} and # other people a gift (\#=#)}
930
 *           }}</p>
931
 *   </div>
932
 * </file>
933
 *
934
 * <file name="script.js">
935
 *   function Person(name, gender) {
936
 *     this.name = name;
937
 *     this.gender = gender;
938
 *   }
939
 *
940
 *   var alice   = new Person('Alice', 'female'),
941
 *       bob     = new Person('Bob', 'male'),
942
 *       sarah     = new Person('Sarah', 'female'),
943
 *       harry   = new Person('Harry Potter', 'male'),
944
 *       ashley   = new Person('Ashley', '');
945
 *
946
 *   angular.module('msgFmtExample', ['ngMessageFormat'])
947
 *     .controller('AppController', ['$scope', function($scope) {
948
 *         $scope.people = [alice, bob, sarah, ashley];
949
 *         $scope.recipients = [alice, bob, sarah];
950
 *         $scope.sender = harry;
951
 *       }]);
952
 * </file>
953
 *
954
 * <file name="protractor.js" type="protractor">
955
 *   describe('MessageFormat plural', function() {
956
 *
957
 *     it('should pluralize initial values', function() {
958
 *       var messageElem = element(by.binding('recipients.length')),
959
 *           decreaseRecipientsBtn = element(by.id('decreaseRecipients'));
960
 *
961
 *       expect(messageElem.getText()).toEqual('Harry Potter gave Alice and 2 other people a gift (#=2)');
962
 *       decreaseRecipientsBtn.click();
963
 *       expect(messageElem.getText()).toEqual('Harry Potter gave Alice and one other person a gift (#=1)');
964
 *       decreaseRecipientsBtn.click();
965
 *       expect(messageElem.getText()).toEqual('Harry Potter gave a gift to Alice (#=0)');
966
 *       decreaseRecipientsBtn.click();
967
 *       expect(messageElem.getText()).toEqual('Harry Potter gave no gifts (#=-1)');
968
 *     });
969
 *   });
970
 * </file>
971
 * </example>
972
 *
973
 * ### Plural and Gender together
974
 *
975
 * This example shows how you can specify gender rules for specific plural matches - in this case,
976
 * =1 is special cased for gender.
977
 * <example name="ngMessageFormat-example-plural-gender" module="msgFmtExample" deps="angular-message-format.js">
978
 *   <file name="index.html">
979
 *     <div ng-controller="AppController">
980
       Select recipients:<br>
981
       <select multiple size=5 ng-model="recipients" ng-options="person as person.name for person in people">
982
       </select><br>
983
        <p>{{recipients.length, plural,
984
          =0 {{{sender.name}} has not given any gifts to anyone.}
985
          =1 {  {{recipients[0].gender, select,
986
                 female { {{sender.name}} gave {{recipients[0].name}} her gift.}
987
                 male { {{sender.name}} gave {{recipients[0].name}} his gift.}
988
                 other { {{sender.name}} gave {{recipients[0].name}} their gift.}
989
                }}
990
              }
991
          other {{{sender.name}} gave {{recipients.length}} people gifts.}
992
          }}</p>
993
      </file>
994
 *    <file name="script.js">
995
 *      function Person(name, gender) {
996
 *        this.name = name;
997
 *        this.gender = gender;
998
 *      }
999
 *
1000
 *      var alice   = new Person('Alice', 'female'),
1001
 *          bob     = new Person('Bob', 'male'),
1002
 *          harry   = new Person('Harry Potter', 'male'),
1003
 *          ashley   = new Person('Ashley', '');
1004
 *
1005
 *      angular.module('msgFmtExample', ['ngMessageFormat'])
1006
 *        .controller('AppController', ['$scope', function($scope) {
1007
 *            $scope.people = [alice, bob, ashley];
1008
 *            $scope.recipients = [alice];
1009
 *            $scope.sender = harry;
1010
 *          }]);
1011
 *    </file>
1012
    </example>
1013
 */
1014
1015
var $$MessageFormatFactory = ['$parse', '$locale', '$sce', '$exceptionHandler', function $$messageFormat(
1016
                               $parse,   $locale,   $sce,   $exceptionHandler) {
1017
1018
  function getStringifier(trustedContext, allOrNothing, text) {
1019
    return function stringifier(value) {
1020
      try {
1021
        value = trustedContext ? $sce['getTrusted'](trustedContext, value) : $sce['valueOf'](value);
1022
        return allOrNothing && (value === undefined) ? value : $$stringify(value);
1023
      } catch (err) {
1024
        $exceptionHandler($interpolateMinErr['interr'](text, err));
0 ignored issues
show
There is no return statement in this branch, but you do return something in other branches. Did you maybe miss it? If you do not want to return anything, consider adding return undefined; explicitly.
Loading history...
1025
      }
1026
    };
1027
  }
1028
1029
  function interpolate(text, mustHaveExpression, trustedContext, allOrNothing) {
1030
    var stringifier = getStringifier(trustedContext, allOrNothing, text);
1031
    var parser = new MessageFormatParser(text, 0, $parse, $locale['pluralCat'], stringifier,
1032
                                         mustHaveExpression, trustedContext, allOrNothing);
1033
    parser.run(parser.ruleInterpolate);
1034
    return parser.parsedFn;
1035
  }
1036
1037
  return {
1038
    'interpolate': interpolate
1039
  };
1040
}];
1041
1042
var $$interpolateDecorator = ['$$messageFormat', '$delegate', function $$interpolateDecorator($$messageFormat, $interpolate) {
1043
  if ($interpolate['startSymbol']() !== '{{' || $interpolate['endSymbol']() !== '}}') {
1044
    throw $interpolateMinErr('nochgmustache', 'angular-message-format.js currently does not allow you to use custom start and end symbols for interpolation.');
1045
  }
1046
  var interpolate = $$messageFormat['interpolate'];
1047
  interpolate['startSymbol'] = $interpolate['startSymbol'];
1048
  interpolate['endSymbol'] = $interpolate['endSymbol'];
1049
  return interpolate;
1050
}];
1051
1052
var $interpolateMinErr;
1053
var isFunction;
1054
var noop;
1055
var toJson;
1056
var $$stringify;
1057
1058
var ngModule = window['angular']['module']('ngMessageFormat', ['ng']);
1059
ngModule['info']({ 'angularVersion': '1.8.2' });
1060
ngModule['factory']('$$messageFormat', $$MessageFormatFactory);
1061
ngModule['config'](['$provide', function($provide) {
1062
  $interpolateMinErr = window['angular']['$interpolateMinErr'];
1063
  isFunction = window['angular']['isFunction'];
1064
  noop = window['angular']['noop'];
1065
  toJson = window['angular']['toJson'];
1066
  $$stringify = window['angular']['$$stringify'];
1067
1068
  $provide['decorator']('$interpolate', $$interpolateDecorator);
1069
}]);
1070
1071
1072
})(window, window.angular);
1073