docs: private ParseResult parseWithVersions() throws IOException {
      List<TemplateMetadata> templateMetadatas = new ArrayList<>();
      for (CompilationUnitAndKind unit : compilationUnits()) {
        templateMetadatas.addAll(
            TemplateMetadataSerializer.templatesFromCompilationUnit(
                unit.compilationUnit(),
                unit.fileKind(),
                typeRegistry(),
                unit.filePath(),
                errorReporter()));
      }
      IdGenerator nodeIdGen =
          (cache() != null) ? cache().getNodeIdGenerator() : new IncrementingIdGenerator();
      SoyFileSetNode soyTree = new SoyFileSetNode(nodeIdGen.genId(), nodeIdGen);
      boolean filesWereSkipped = false;
      // TODO(lukes): there are other places in the compiler (autoescaper) which may use the id
      // generator but fail to lock on it.  Eliminate the id system to avoid this whole issue.
      synchronized (nodeIdGen) { // Avoid using the same ID generator in multiple threads.
        for (SoyFileSupplier fileSupplier : soyFileSuppliers().values()) {
          SoyFileSupplier.Version version = fileSupplier.getVersion();
          VersionedFile cachedFile =
              cache() != null ? cache().get(fileSupplier.getFilePath(), version) : null;
          SoyFileNode node;
          if (cachedFile == null) {
            node = parseSoyFileHelper(fileSupplier, nodeIdGen);
            // TODO(b/19269289): implement error recovery and keep on trucking in order to display
            // as many errors as possible. Currently, the later passes just spew NPEs if run on
            // a malformed parse tree.
            if (node == null) {
              filesWereSkipped = true;
              continue;
            }
            // Run passes that are considered part of initial parsing.
            passManager().runSingleFilePasses(node, nodeIdGen);
            // Run passes that check the tree.
            if (cache() != null) {
              cache().put(fileSupplier.getFilePath(), VersionedFile.of(node, version));
            }
          } else {
            node = cachedFile.file();
          }
          for (TemplateNode template : node.getChildren()) {
            templateMetadatas.add(TemplateMetadata.fromTemplate(template));
          }
          soyTree.addChild(node);
        }
  
        TemplateRegistry registry = new TemplateRegistry(templateMetadatas, errorReporter());
        // Run passes that check the tree iff we successfully parsed every file.
        if (!filesWereSkipped) {
          passManager().runWholeFilesetPasses(soyTree, registry);
        }
        return ParseResult.create(soyTree, registry);
      }
    }