/*
* Copyright 2008 The Closure Compiler Authors.
*
* Licensed under the Apache License, Version 2.0 (the "License");
* you may not use this file except in compliance with the License.
* You may obtain a copy of the License at
*
* http://www.apache.org/licenses/LICENSE-2.0
*
* Unless required by applicable law or agreed to in writing, software
* distributed under the License is distributed on an "AS IS" BASIS,
* WITHOUT WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied.
* See the License for the specific language governing permissions and
* limitations under the License.
*/
package com.google.javascript.jscomp;
import static com.google.javascript.jscomp.JsMessage.Style.RELAX;
import static com.google.javascript.jscomp.JsMessageVisitor.MESSAGE_TREE_MALFORMED;
import com.google.javascript.jscomp.JsMessage.Style;
import java.util.HashMap;
import java.util.Map;
/**
* Test which checks that replacer works correctly.
*
*/
public final class ReplaceMessagesTest extends CompilerTestCase {
private Map<String, JsMessage> messages;
private Style style;
private boolean strictReplacement;
@Override
protected CompilerPass getProcessor(Compiler compiler) {
return new ReplaceMessages(compiler,
new SimpleMessageBundle(), false, style, strictReplacement);
}
@Override
protected int getNumRepetitions() {
// No longer valid on the second run.
return 1;
}
@Override
protected void setUp() throws Exception {
super.setUp();
messages = new HashMap<>();
strictReplacement = false;
style = RELAX;
}
public void testReplaceSimpleMessage() {
registerMessage(new JsMessage.Builder("MSG_A")
.appendStringPart("Hi\nthere")
.build());
test(
"/** @desc d */\n var MSG_A = goog.getMsg('asdf');",
"/** @desc d */\n var MSG_A='Hi\\nthere'");
}
public void testNameReplacement() {
registerMessage(new JsMessage.Builder("MSG_B")
.appendStringPart("One ")
.appendPlaceholderReference("measly")
.appendStringPart(" ph")
.build());
test(
"/** @desc d */\n var MSG_B=goog.getMsg('asdf {$measly}', {measly: x});",
"/** @desc d */\n var MSG_B='One '+ (x +' ph' )");
}
public void testGetPropReplacement() {
registerMessage(new JsMessage.Builder("MSG_C")
.appendPlaceholderReference("amount")
.build());
test(
"/** @desc d */\n var MSG_C = goog.getMsg('${$amount}', {amount: a.b.amount});",
"/** @desc d */\n var MSG_C=a.b.amount");
}
public void testFunctionCallReplacement() {
registerMessage(new JsMessage.Builder("MSG_D")
.appendPlaceholderReference("amount")
.build());
test(
"/** @desc d */\n var MSG_D = goog.getMsg('${$amount}', {amount: getAmt()});",
"/** @desc d */\n var MSG_D=getAmt()");
}
public void testMethodCallReplacement() {
registerMessage(new JsMessage.Builder("MSG_E")
.appendPlaceholderReference("amount")
.build());
test(
"/** @desc d */\n var MSG_E = goog.getMsg('${$amount}', {amount: obj.getAmt()});",
"/** @desc d */\n var MSG_E=obj.getAmt()");
}
public void testHookReplacement() {
registerMessage(new JsMessage.Builder("MSG_F")
.appendStringPart("#")
.appendPlaceholderReference("amount")
.appendStringPart(".")
.build());
test(
"/** @desc d */\n var MSG_F = goog.getMsg('${$amount}', {amount: (a ? b : c)});",
"/** @desc d */\n var MSG_F='#'+((a?b:c)+'.')");
}
public void testAddReplacement() {
registerMessage(new JsMessage.Builder("MSG_G")
.appendPlaceholderReference("amount")
.build());
test(
"/** @desc d */\n var MSG_G = goog.getMsg('${$amount}', {amount: x + ''});",
"/** @desc d */\n var MSG_G=x+''");
}
public void testPlaceholderValueReferencedTwice() {
registerMessage(new JsMessage.Builder("MSG_H")
.appendPlaceholderReference("dick")
.appendStringPart(", ")
.appendPlaceholderReference("dick")
.appendStringPart(" and ")
.appendPlaceholderReference("jane")
.build());
test(
"/** @desc d */\n var MSG_H = goog.getMsg('{$dick}{$jane}', {jane: x, dick: y});",
"/** @desc d */\n var MSG_H = y + (', ' + (y + (' and ' + x)))");
}
public void testPlaceholderNameInLowerCamelCase() {
registerMessage(new JsMessage.Builder("MSG_I")
.appendStringPart("Sum: $")
.appendPlaceholderReference("amtEarned")
.build());
test(
"/** @desc d */\n var MSG_I = goog.getMsg('${$amtEarned}', {amtEarned: x});",
"/** @desc d */\n var MSG_I='Sum: $'+x");
}
public void testQualifiedMessageName() {
registerMessage(new JsMessage.Builder("MSG_J")
.appendStringPart("One ")
.appendPlaceholderReference("measly")
.appendStringPart(" ph")
.build());
test(
"/** @desc d */\n a.b.c.MSG_J = goog.getMsg('asdf {$measly}', {measly: x});",
"/** @desc d */\n a.b.c.MSG_J='One '+(x+' ph')");
}
public void testPlaceholderInPlaceholderValue() {
registerMessage(new JsMessage.Builder("MSG_L")
.appendPlaceholderReference("a")
.appendStringPart(" has ")
.appendPlaceholderReference("b")
.build());
test(
"/** @desc d */\n var MSG_L = goog.getMsg('{$a} has {$b}', {a: '{$b}', b: 1});",
"/** @desc d */\n var MSG_L='{$b}'+(' has '+1);");
}
public void testSimpleMessageReplacementMissing() {
style = Style.LEGACY;
test(
"/** @desc d */\n var MSG_E = 'd*6a0@z>t';",
"/** @desc d */\n var MSG_E = 'd*6a0@z>t'");
}
public void testSimpleMessageReplacementMissingWithNewStyle() {
test(
"/** @desc d */\n var MSG_E = goog.getMsg('missing');",
"/** @desc d */\n var MSG_E = 'missing'");
}
public void testStrictModeAndMessageReplacementAbsentInBundle() {
style = Style.LEGACY;
strictReplacement = true;
testError("var MSG_E = 'Hello';",
ReplaceMessages.BUNDLE_DOES_NOT_HAVE_THE_MESSAGE);
}
public void testStrictModeAndMessageReplacementAbsentInNonEmptyBundle() {
style = Style.LEGACY;
registerMessage(new JsMessage.Builder("MSG_J")
.appendStringPart("One ")
.appendPlaceholderReference("measly")
.appendStringPart(" ph")
.build());
strictReplacement = true;
testError("var MSG_E = 'Hello';",
ReplaceMessages.BUNDLE_DOES_NOT_HAVE_THE_MESSAGE);
}
public void testFunctionReplacementMissing() {
style = Style.LEGACY;
test("var MSG_F = function() {return 'asdf'};",
"var MSG_F = function() {return'asdf'}");
}
public void testFunctionWithParamReplacementMissing() {
style = Style.LEGACY;
test(
"var MSG_G = function(measly) {return 'asdf' + measly};",
"var MSG_G=function(measly){return'asdf'+measly}");
}
public void testPlaceholderNameInLowerUnderscoreCase() {
testError(
"var MSG_J = goog.getMsg('${$amt_earned}', {amt_earned: x});",
MESSAGE_TREE_MALFORMED);
}
public void testBadPlaceholderReferenceInReplacement() {
registerMessage(new JsMessage.Builder("MSG_K")
.appendPlaceholderReference("amount")
.build());
testError(
"var MSG_K = goog.getMsg('Hi {$jane}', {jane: x});",
MESSAGE_TREE_MALFORMED);
}
public void testEmptyObjLit() {
registerMessage(new JsMessage.Builder("MSG_E")
.appendPlaceholderReference("amount")
.build());
testError(
"/** @desc d */\nvar MSG_E = goog.getMsg('');",
MESSAGE_TREE_MALFORMED,
"Message parse tree malformed. "
+ "Empty placeholder value map for a translated message "
+ "with placeholders.");
}
public void testLegacyStyleNoPlaceholdersVarSyntax() {
registerMessage(new JsMessage.Builder("MSG_A")
.appendStringPart("Hi\nthere")
.build());
style = Style.LEGACY;
test("var MSG_A = 'd*6a0@z>t';",
"var MSG_A='Hi\\nthere'");
}
public void testLegacyStyleNoPlaceholdersFunctionSyntax() {
registerMessage(new JsMessage.Builder("MSG_B")
.appendStringPart("Hi\nthere")
.build());
style = Style.LEGACY;
test("var MSG_B = function() {return 'asdf'};",
"var MSG_B=function(){return'Hi\\nthere'}");
}
public void testLegacyStyleOnePlaceholder() {
registerMessage(new JsMessage.Builder("MSG_C")
.appendStringPart("One ")
.appendPlaceholderReference("measly")
.appendStringPart(" ph")
.build());
style = Style.LEGACY;
test(
"var MSG_C = function(measly) {return 'asdf' + measly};",
"var MSG_C=function(measly){return'One '+(measly+' ph')}");
}
public void testLegacyStyleTwoPlaceholders() {
registerMessage(new JsMessage.Builder("MSG_D")
.appendPlaceholderReference("dick")
.appendStringPart(" and ")
.appendPlaceholderReference("jane")
.build());
style = Style.LEGACY;
test(
"var MSG_D = function(jane, dick) {return jane + dick};",
"var MSG_D=function(jane,dick){return dick+(' and '+jane)}");
}
public void testLegacyStylePlaceholderNameInLowerCamelCase() {
registerMessage(new JsMessage.Builder("MSG_E")
.appendStringPart("Sum: $")
.appendPlaceholderReference("amtEarned")
.build());
style = Style.LEGACY;
test(
"var MSG_E = function(amtEarned) {return amtEarned + 'x'};",
"var MSG_E=function(amtEarned){return'Sum: $'+amtEarned}");
}
public void testLegacyStylePlaceholderNameInLowerUnderscoreCase() {
registerMessage(new JsMessage.Builder("MSG_F")
.appendStringPart("Sum: $")
.appendPlaceholderReference("amt_earned")
.build());
// Placeholder named in lower-underscore case (discouraged nowadays)
style = Style.LEGACY;
test(
"var MSG_F = function(amt_earned) {return amt_earned + 'x'};",
"var MSG_F=function(amt_earned){return'Sum: $'+amt_earned}");
}
public void testLegacyStyleBadPlaceholderReferenceInReplacemen() {
style = Style.LEGACY;
registerMessage(new JsMessage.Builder("MSG_B")
.appendStringPart("Ola, ")
.appendPlaceholderReference("chimp")
.build());
testError("var MSG_B = function(chump) {return chump + 'x'};",
JsMessageVisitor.MESSAGE_TREE_MALFORMED);
}
public void testTranslatedPlaceHolderMissMatch() {
registerMessage(new JsMessage.Builder("MSG_A")
.appendPlaceholderReference("a")
.appendStringPart("!")
.build());
testError("var MSG_A = goog.getMsg('{$a}');",
MESSAGE_TREE_MALFORMED);
}
public void testBadFallbackSyntax1() {
testError("/** @desc d */\n" +
"var MSG_A = goog.getMsg('asdf');" +
"var x = goog.getMsgWithFallback(MSG_A);",
JsMessageVisitor.BAD_FALLBACK_SYNTAX);
}
public void testBadFallbackSyntax2() {
testError("var x = goog.getMsgWithFallback('abc', 'bcd');",
JsMessageVisitor.BAD_FALLBACK_SYNTAX);
}
public void testBadFallbackSyntax3() {
testError("/** @desc d */\n" +
"var MSG_A = goog.getMsg('asdf');" +
"var x = goog.getMsgWithFallback(MSG_A, y);",
JsMessageVisitor.FALLBACK_ARG_ERROR);
}
public void testBadFallbackSyntax4() {
testError("/** @desc d */\n" +
"var MSG_A = goog.getMsg('asdf');" +
"var x = goog.getMsgWithFallback(y, MSG_A);",
JsMessageVisitor.FALLBACK_ARG_ERROR);
}
public void testUseFallback() {
registerMessage(new JsMessage.Builder("MSG_B")
.appendStringPart("translated")
.build());
test(
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = goog.getMsg('msg A');",
"/** @desc d */",
"var MSG_B = goog.getMsg('msg B');",
"var x = goog.getMsgWithFallback(MSG_A, MSG_B);"),
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = 'msg A';",
"/** @desc d */",
"var MSG_B = 'translated';",
"var x = MSG_B;"));
}
public void testFallbackEmptyBundle() {
test(
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = goog.getMsg('msg A');",
"/** @desc d */",
"var MSG_B = goog.getMsg('msg B');",
"var x = goog.getMsgWithFallback(MSG_A, MSG_B);"),
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = 'msg A';",
"/** @desc d */",
"var MSG_B = 'msg B';",
"var x = MSG_A;"));
}
public void testNoUseFallback() {
registerMessage(new JsMessage.Builder("MSG_A")
.appendStringPart("translated")
.build());
test(
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = goog.getMsg('msg A');",
"/** @desc d */",
"var MSG_B = goog.getMsg('msg B');",
"var x = goog.getMsgWithFallback(MSG_A, MSG_B);"),
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = 'translated';",
"/** @desc d */",
"var MSG_B = 'msg B';",
"var x = MSG_A;"));
}
public void testNoUseFallback2() {
registerMessage(new JsMessage.Builder("MSG_C")
.appendStringPart("translated")
.build());
test(
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = goog.getMsg('msg A');",
"/** @desc d */",
"var MSG_B = goog.getMsg('msg B');",
"var x = goog.getMsgWithFallback(MSG_A, MSG_B);"),
LINE_JOINER.join(
"/** @desc d */",
"var MSG_A = 'msg A';",
"/** @desc d */",
"var MSG_B = 'msg B';",
"var x = MSG_A;"));
}
private void registerMessage(JsMessage message) {
messages.put(message.getKey(), message);
}
private class SimpleMessageBundle implements MessageBundle {
@Override
public JsMessage getMessage(String id) {
return messages.get(id);
}
@Override
public Iterable<JsMessage> getAllMessages() {
return messages.values();
}
@Override
public JsMessage.IdGenerator idGenerator() {
return null;
}
}
}