/**
   * Compiles this Soy file set into JS source code files and returns these JS files as a list of
   * strings, one per file.
   *
   * @param jsSrcOptions The compilation options for the JS Src output target.
   * @param msgBundle The bundle of translated messages, or null to use the messages from the Soy
   *     source.
   * @return A list of strings where each string represents the JS source code that belongs in one
   *     JS file. The generated JS files correspond one-to-one to the original Soy source files.
   * @throws SoySyntaxException If a syntax error is found.
   */
  public List<String> compileToJsSrc(SoyJsSrcOptions jsSrcOptions, @Nullable SoyMsgBundle msgBundle)
      throws SoySyntaxException {

    boolean doEnforceSyntaxVersionV2 = !jsSrcOptions.shouldAllowDeprecatedSyntax();
    SoyFileSetNode soyTree =
        (new SoyFileSetParser(soyFileSuppliers))
            .setDoEnforceSyntaxVersionV2(doEnforceSyntaxVersionV2)
            .parse();
    runMiddleendPasses(soyTree, doEnforceSyntaxVersionV2);

    return jsSrcMainProvider.get().genJsSrc(soyTree, jsSrcOptions, msgBundle);
  }
  /**
   * 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);
      }
    }
  }
  /**
   * Generates Incremental DOM JS source code given a Soy parse tree, an options object, and an
   * optional bundle of translated messages.
   *
   * @param soyTree The Soy parse tree to generate JS source code for.
   * @param jsSrcOptions The compilation options relevant to this backend.
   * @return A list of strings where each string represents the JS source code that belongs in one
   *     JS file. The generated JS files correspond one-to-one to the original Soy source files.
   * @throws SoySyntaxException If a syntax error is found.
   */
  public List<String> genJsSrc(SoyFileSetNode soyTree, SoyJsSrcOptions jsSrcOptions)
      throws SoySyntaxException {

    // Generate code with the opt_ijData param if either (a) the user specified the compiler flag
    // --isUsingIjData or (b) any of the Soy code in the file set references injected data.
    boolean isUsingIjData =
        jsSrcOptions.isUsingIjData() || new IsUsingIjDataVisitor().exec(soyTree);

    // Make sure that we don't try to use goog.i18n.bidi when we aren't supposed to use Closure.
    Preconditions.checkState(
        !jsSrcOptions.getUseGoogIsRtlForBidiGlobalDir()
            || jsSrcOptions.shouldProvideRequireSoyNamespaces()
            || jsSrcOptions.shouldProvideRequireJsFunctions(),
        "Do not specify useGoogIsRtlForBidiGlobalDir without either"
            + " shouldProvideRequireSoyNamespaces or shouldProvideRequireJsFunctions.");

    try (WithScope withScope = apiCallScope.enter()) {
      // Seed the scoped parameters.
      apiCallScope.seed(SoyJsSrcOptions.class, jsSrcOptions);
      apiCallScope.seed(Key.get(Boolean.class, IsUsingIjData.class), isUsingIjData);
      BidiGlobalDir bidiGlobalDir =
          SoyBidiUtils.decodeBidiGlobalDirFromJsOptions(
              jsSrcOptions.getBidiGlobalDir(), jsSrcOptions.getUseGoogIsRtlForBidiGlobalDir());
      ApiCallScopeUtils.seedSharedParams(apiCallScope, null /* msgBundle */, bidiGlobalDir);

      // TODO(sparhami) figure out how to deal with msg nodes - need to support some sort of
      // innerHTML,
      // which means we need autoescaping for just those subtrees.
      //      new ReplaceMsgsWithGoogMsgsVisitor(errorReporter).exec(soyTree);
      //      new MoveGoogMsgDefNodesEarlierVisitor(errorReporter).exec(soyTree);
      //      Preconditions.checkState(
      //          bidiGlobalDir != null,
      //          "If enabling shouldGenerateGoogMsgDefs, must also set bidi global
      // directionality.");

      // Do the code generation.
      optimizeBidiCodeGenVisitorProvider.get().exec(soyTree);
      simplifyVisitor.exec(soyTree);

      new HtmlTransformVisitor(errorReporter).exec(soyTree);
      IncrementalDomOutputOptimizers.collapseOpenTags(soyTree);
      IncrementalDomOutputOptimizers.collapseElements(soyTree);

      return genIncrementalDomCodeVisitorProvider.get().exec(soyTree);
    }
  }
  public void testMapLiteral() {

    // ------ Unquoted keys. ------
    assertTranslation("[:]", new JsExpr("{}", Integer.MAX_VALUE));
    assertTranslation(
        "['aaa': 123, 'bbb': 'blah']", new JsExpr("{aaa: 123, bbb: 'blah'}", Integer.MAX_VALUE));
    assertTranslation(
        "['aaa': $foo, 'bbb': 'blah']",
        new JsExpr("{aaa: opt_data.foo, bbb: 'blah'}", Integer.MAX_VALUE));

    // ------ Quoted keys. ------
    assertTranslation("quoteKeysIfJs([:])", new JsExpr("{}", Integer.MAX_VALUE));
    assertTranslation(
        "quoteKeysIfJs( ['aaa': $foo, 'bbb': 'blah'] )",
        new JsExpr("{'aaa': opt_data.foo, 'bbb': 'blah'}", Integer.MAX_VALUE));
    assertTranslation(
        "quoteKeysIfJs(['aaa': 123, $boo: $foo])",
        new JsExpr(
            "(function() { var map_s = {'aaa': 123};"
                + " map_s[soy.$$checkMapKey(opt_data.boo)] = opt_data.foo;"
                + " return map_s; })()",
            Integer.MAX_VALUE));
    assertTranslation(
        "quoteKeysIfJs([$boo: $foo, $goo[0]: 123])",
        new JsExpr(
            "(function() { var map_s = {};"
                + " map_s[soy.$$checkMapKey(opt_data.boo)] = opt_data.foo;"
                + " map_s[soy.$$checkMapKey(gooData8[0])] = 123;"
                + " return map_s; })()",
            Integer.MAX_VALUE));

    // ------ Errors. ------

    // Non-string key is error.
    assertSoyErrors(
        "[0: 123, 1: 'oops']",
        "Keys in map literals cannot be constants (found constant '0').",
        "Keys in map literals cannot be constants (found constant '1').");

    SoyJsSrcOptions jsSrcOptionsWithoutCompiler = new SoyJsSrcOptions();
    SoyJsSrcOptions jsSrcOptionsWithCompiler = new SoyJsSrcOptions();
    jsSrcOptionsWithCompiler.setShouldGenerateJsdoc(true);

    // Non-identifier key without quoteKeysIfJs() is error only when using Closure Compiler.
    assertTranslation(
        "['0': 123, '1': $foo]",
        new JsExpr("{'0': 123, '1': opt_data.foo}", Integer.MAX_VALUE),
        jsSrcOptionsWithoutCompiler);
    assertSoyErrors(
        "['0': 123, '1': '123']",
        jsSrcOptionsWithCompiler,
        "Map literal with non-identifier key '0' must be wrapped in quoteKeysIfJs().",
        "Map literal with non-identifier key '1' must be wrapped in quoteKeysIfJs().");

    // Expression key without quoteKeysIfJs() is error only when using Closure Compiler.
    assertTranslation(
        "['aaa': 123, $boo: $foo]",
        new JsExpr(
            "(function() { var map_s = {aaa: 123};"
                + " map_s[soy.$$checkMapKey(opt_data.boo)] = opt_data.foo;"
                + " return map_s; })()",
            Integer.MAX_VALUE),
        jsSrcOptionsWithoutCompiler);
    assertSoyErrors(
        "['aaa': 123, $boo: $foo]",
        jsSrcOptionsWithCompiler,
        "Expression key '$boo' in map literal must be wrapped in quoteKeysIfJs().");
  }