/** * Compiles this Soy file set into JS source code files and writes these JS files to disk. * * @param outputPathFormat The format string defining how to build the output file path * corresponding to an input file path. * @param inputFilePathPrefix The prefix prepended to all input file paths (can be empty string). * @param jsSrcOptions The compilation options for the JS Src output target. * @param locales The list of locales. Can be an empty list if not applicable. * @param messageFilePathFormat The message file path format, or null if not applicable. * @throws SoySyntaxException If a syntax error is found. * @throws IOException If there is an error in opening/reading a message file or opening/writing * an output JS file. */ void compileToJsSrcFiles( String outputPathFormat, String inputFilePathPrefix, SoyJsSrcOptions jsSrcOptions, List<String> locales, @Nullable String messageFilePathFormat) throws SoySyntaxException, IOException { boolean doEnforceSyntaxVersionV2 = !jsSrcOptions.shouldAllowDeprecatedSyntax(); SoyFileSetNode soyTree = (new SoyFileSetParser(soyFileSuppliers)) .setDoEnforceSyntaxVersionV2(doEnforceSyntaxVersionV2) .parse(); runMiddleendPasses(soyTree, doEnforceSyntaxVersionV2); if (locales.size() == 0) { // Not generating localized JS. jsSrcMainProvider .get() .genJsFiles(soyTree, jsSrcOptions, null, null, outputPathFormat, inputFilePathPrefix); } else { // Generating localized JS. for (String locale : locales) { SoyFileSetNode soyTreeClone = soyTree.clone(); String msgFilePath = JsSrcUtils.buildFilePath(messageFilePathFormat, locale, null, inputFilePathPrefix); SoyMsgBundle msgBundle = msgBundleHandlerProvider.get().createFromFile(new File(msgFilePath)); if (msgBundle.getLocaleString() == null) { // TODO: Remove this check (but make sure no projects depend on this behavior). // There was an error reading the message file. We continue processing only if the locale // begins with "en", because falling back to the Soy source will proably be fine. if (!locale.startsWith("en")) { throw new IOException("Error opening or reading message file " + msgFilePath); } } jsSrcMainProvider .get() .genJsFiles( soyTreeClone, jsSrcOptions, locale, msgBundle, outputPathFormat, inputFilePathPrefix); } } }
/** * Returns the contextually rewritten source. * * <p>The Soy tree may have multiple files, but only the source code for the first is returned. */ private void assertInjected(String expectedOutput, String input) throws SoyAutoescapeException { SoyFileSetNode soyTree = parseAndInjectIntoScriptTags(input, " INJE='CTED'"); StringBuilder src = new StringBuilder(); src.append(soyTree.getChild(0).toSourceString()); String output = src.toString().trim(); if (output.startsWith("{namespace ns")) { output = output.substring(output.indexOf('}') + 1).trim(); } assertThat(output).isEqualTo(expectedOutput); }
public void testFindCalleesNotInFile() { String testFileContent = "" + "{namespace boo.foo autoescape=\"deprecated-noncontextual\"}\n" + "\n" + "/** Test template 1. */\n" + "{template .goo}\n" + " {call .goo data=\"all\" /}\n" + " {call .moo data=\"all\" /}\n" + " {call boo.woo.hoo data=\"all\" /}\n" + // not defined in this file "{/template}\n" + "\n" + "/** Test template 2. */\n" + "{template .moo}\n" + " {for $i in range(8)}\n" + " {call boo.foo.goo data=\"all\" /}\n" + " {call .too data=\"all\" /}\n" + // not defined in this file " {call .goo}" + " {param a}{call .moo /}{/param}" + " {param b}{call .zoo /}{/param}" + // not defined in this file " {/call}" + " {/for}\n" + "{/template}\n" + "\n" + "/** Test template 3. */\n" + "{deltemplate booHoo}\n" + " {call .goo data=\"all\" /}\n" + " {call .moo data=\"all\" /}\n" + " {call boo.hoo.roo data=\"all\" /}\n" + // not defined in this file "{/deltemplate}\n"; ErrorReporter boom = ExplodingErrorReporter.get(); SoyFileSetNode soyTree = SoyFileSetParserBuilder.forFileContents(testFileContent).errorReporter(boom).parse(); SoyFileNode soyFile = soyTree.getChild(0); Set<String> calleesNotInFile = new FindCalleesNotInFileVisitor(boom).exec(soyFile); assertThat(calleesNotInFile).doesNotContain("boo.foo.goo"); assertThat(calleesNotInFile).doesNotContain("boo.foo.moo"); assertThat(calleesNotInFile).contains("boo.woo.hoo"); assertThat(calleesNotInFile).contains("boo.foo.too"); assertThat(calleesNotInFile).contains("boo.foo.zoo"); assertThat(calleesNotInFile).contains("boo.hoo.roo"); }
private SoyFileSetNode parseAndInjectIntoScriptTags(String input, String toInject) { String namespace = "{namespace ns autoescape=\"deprecated-contextual\"}\n\n"; ErrorReporter boom = ExplodingErrorReporter.get(); SoyFileSetNode soyTree = SoyFileSetParserBuilder.forFileContents(namespace + input).errorReporter(boom).parse(); ContextualAutoescaper contextualAutoescaper = new ContextualAutoescaper(SOY_PRINT_DIRECTIVES, boom); List<TemplateNode> extras = contextualAutoescaper.rewrite(soyTree); SoyFileNode file = soyTree.getChild(soyTree.numChildren() - 1); file.addChildren(file.numChildren(), extras); insertTextAtEndOfScriptOpenTag(contextualAutoescaper.getSlicedRawTextNodes(), toInject); return soyTree; }
private void doContextualEscaping(SoyFileSetNode soyTree) throws SoySyntaxException { List<TemplateNode> extraTemplates = contextualAutoescaper.rewrite(soyTree); // TODO: Run the redundant template remover here and rename after CL 16642341 is in. if (!extraTemplates.isEmpty()) { // TODO: pull out somewhere else. Ideally do the merge as part of the redundant template // removal. Map<String, SoyFileNode> containingFile = Maps.newHashMap(); for (SoyFileNode fileNode : soyTree.getChildren()) { for (TemplateNode templateNode : fileNode.getChildren()) { String name = templateNode instanceof TemplateDelegateNode ? ((TemplateDelegateNode) templateNode).getDelTemplateName() : templateNode.getTemplateName(); containingFile.put(DerivedTemplateUtils.getBaseName(name), fileNode); } } for (TemplateNode extraTemplate : extraTemplates) { String name = extraTemplate instanceof TemplateDelegateNode ? ((TemplateDelegateNode) extraTemplate).getDelTemplateName() : extraTemplate.getTemplateName(); containingFile.get(DerivedTemplateUtils.getBaseName(name)).addChild(extraTemplate); } } }
/** * Generates Incremental DOM JS source files given a Soy parse tree, an options object, an * optional bundle of translated messages, and information on where to put the output files. * * @param soyTree The Soy parse tree to generate JS source code for. * @param jsSrcOptions The compilation options relevant to this backend. * @param outputPathFormat The format string defining how to build the output file path * corresponding to an input file path. * @throws SoySyntaxException If a syntax error is found. * @throws IOException If there is an error in opening/writing an output JS file. */ public void genJsFiles( SoyFileSetNode soyTree, SoyJsSrcOptions jsSrcOptions, String outputPathFormat) throws SoySyntaxException, IOException { List<String> jsFileContents = genJsSrc(soyTree, jsSrcOptions); ImmutableList<SoyFileNode> srcsToCompile = ImmutableList.copyOf( Iterables.filter(soyTree.getChildren(), SoyFileNode.MATCH_SRC_FILENODE)); if (srcsToCompile.size() != jsFileContents.size()) { throw new AssertionError( String.format( "Expected to generate %d code chunk(s), got %d", srcsToCompile.size(), jsFileContents.size())); } Multimap<String, Integer> outputs = MainEntryPointUtils.mapOutputsToSrcs( null /* locale */, outputPathFormat, "" /* inputPathsPrefix */, srcsToCompile); for (String outputFilePath : outputs.keySet()) { Writer out = Files.newWriter(new File(outputFilePath), UTF_8); try { boolean isFirst = true; for (int inputFileIndex : outputs.get(outputFilePath)) { if (isFirst) { isFirst = false; } else { // Concatenating JS files is not safe unless we know that the last statement from one // couldn't combine with the isFirst statement of the next. Inserting a semicolon will // prevent this from happening. out.write("\n;\n"); } out.write(jsFileContents.get(inputFileIndex)); } } finally { out.close(); } } }
/** Visits a {@link SoyFileNode}, getting its id generator. */ @Override protected void visitSoyFileSetNode(SoyFileSetNode node) { idGen = node.getNodeIdGenerator(); visitChildren(node); }