/* * Copyright 2011 Google Inc. * * 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.template.soy.passes; import static com.google.common.truth.Truth.assertThat; import com.google.common.base.Joiner; import com.google.common.collect.Iterables; import com.google.template.soy.SoyFileSetParserBuilder; import com.google.template.soy.basetree.SyntaxVersion; import com.google.template.soy.error.FormattingErrorReporter; import org.junit.Test; import org.junit.runner.RunWith; import org.junit.runners.JUnit4; /** @author Mike Samuel */ @RunWith(JUnit4.class) public final class CheckFunctionCallsVisitorTest { @Test public void testPureFunctionOk() { assertSuccess( "{namespace ns}\n", "/**", " * @param x", " * @param y", " */", "{template .foo}", " {print min($x, $y)}", "{/template}"); } @Test public void testIncorrectArity() { assertFunctionCallsInvalid( "Function 'min' called with 1 arguments (expected 2).", "{namespace ns}\n", "/**", " * @param x", " */", "{template .foo}", " {print min($x)}", "{/template}"); assertFunctionCallsInvalid( "Function 'index' called with 0 arguments (expected 1).", "{namespace ns}\n", "{template .foo}", " {print index()}", "{/template}"); } @Test public void testNestedFunctionCall() { assertFunctionCallsInvalid( "Function 'min' called with 1 arguments (expected 2).", "{namespace ns}\n", "/**", " * @param x", " * @param y", " */", "{template .foo}", " {print min(min($x), min($x, $y))}", "{/template}"); } @Test public void testNotALoopVariable1() { assertFunctionCallsInvalid( "Function 'index' must have a foreach loop variable as its argument", "{namespace ns}\n", "/**", " * @param x", " */", "{template .foo}", " {print index($x)}", "{/template}"); } @Test public void testNotALoopVariable2() { assertFunctionCallsInvalid( "Function 'index' must have a foreach loop variable as its argument", "{namespace ns}\n", "/**", " * @param x", " */", "{template .foo}", " {print index($x.y)}", "{/template}"); } @Test public void testNotALoopVariable3() { assertFunctionCallsInvalid( "Function 'index' must have a foreach loop variable as its argument", "{namespace ns}\n", "{template .foo}", " {print index($ij.data)}", "{/template}"); } @Test public void testNotALoopVariable4() { assertFunctionCallsInvalid( "Function 'index' must have a foreach loop variable as its argument", "{namespace ns}\n", "/**", " * @param x", " */", "{template .foo}", " {print index($x + 1)}", "{/template}"); } @Test public void testLoopVariableOk() { assertSuccess( "{namespace ns}\n", "/**", " * @param elements", " */", "{template .foo}", " {foreach $z in $elements}", " {if isLast($z)}Lorem Ipsum{/if}", " {/foreach}", "{/template}"); } @Test public void testLoopVariableNotInScopeWhenEmpty() { assertFunctionCallsInvalid( "Function 'index' must have a foreach loop variable as its argument", "{namespace ns}\n", "/**", " * @param elements", " */", "{template .foo}", " {foreach $z in $elements}", " Lorem Ipsum...", " {ifempty}", " {print index($elements)}", // Loop variable not in scope when empty. " {/foreach}", "{/template}"); } @Test public void testQuoteKeysIfJsFunction() { assertSuccess( "{namespace ns}\n", "{template .foo}", " {let $m: quoteKeysIfJs(['a': 1, 'b': 'blah']) /}", "{/template}"); assertFunctionCallsInvalid( "Function 'quoteKeysIfJs' called with argument of type string (expected map literal).", "{namespace ns}\n", "{template .foo}", " {let $m: quoteKeysIfJs('blah') /}", "{/template}"); } @Test public void testV1ExpressionFunction() { assertPasses( SyntaxVersion.V1_0, "{namespace ns}\n", "{template .foo deprecatedV1=\"true\"}", " {let $m: v1Expression('blah.length') /}", "{/template}"); assertFunctionCallsInvalid( SyntaxVersion.V1_0, "incorrect v1 syntax: The v1Expression function can only be used in templates " + "marked with the deprecatedV1=\"true\" attribute", "{namespace ns}\n", "{template .foo}", " {let $blah: 'foo' /}", " {let $m: v1Expression('$blah') /}", "{/template}"); assertFunctionCallsInvalid( SyntaxVersion.V1_0, "Function 'v1Expression' called with argument of type string (expected string literal).", "{namespace ns}\n", "{template .foo deprecatedV1=\"true\"}", " {let $blah: 'foo' /}", " {let $m: v1Expression($blah) /}", "{/template}"); } @Test public void testUnrecognizedFunction() { assertFunctionCallsInvalid( "Unknown function 'bogus'.", "{namespace ns}\n", "{template .foo}", " {print bogus()}", "{/template}"); } @Test public void testUnrecognizedFunctionOkInV1() { assertPasses( SyntaxVersion.V1_0, "{namespace ns}\n", "{template .foo}", " {print bogus()}", "{/template}"); } private void assertSuccess(String... lines) { assertPasses(SyntaxVersion.V2_0, lines); } private void assertPasses(SyntaxVersion declaredSyntaxVersion, String... lines) { SoyFileSetParserBuilder.forFileContents(Joiner.on('\n').join(lines)) .declaredSyntaxVersion(declaredSyntaxVersion) .parse() .fileSet(); } private void assertFunctionCallsInvalid(String errorMessage, String... lines) { assertFunctionCallsInvalid(SyntaxVersion.V2_0, errorMessage, lines); } private void assertFunctionCallsInvalid( SyntaxVersion declaredSyntaxVersion, String errorMessage, String... lines) { FormattingErrorReporter errorReporter = new FormattingErrorReporter(); SoyFileSetParserBuilder.forFileContents(Joiner.on('\n').join(lines)) .declaredSyntaxVersion(declaredSyntaxVersion) .errorReporter(errorReporter) .parse(); assertThat(errorReporter.getErrorMessages()).hasSize(1); assertThat(Iterables.getOnlyElement(errorReporter.getErrorMessages())).contains(errorMessage); } }