export function transformFileoverviewCommentFactory()

in src/fileoverview_comment_transformer.ts [98:207]


export function transformFileoverviewCommentFactory(
    options: ts.CompilerOptions, diagnostics: ts.Diagnostic[]) {
  return (): (sourceFile: ts.SourceFile) => ts.SourceFile => {
    function checkNoFileoverviewComments(
        context: ts.Node, comments: jsdoc.SynthesizedCommentWithOriginal[], message: string) {
      for (const comment of comments) {
        const parse = jsdoc.parse(comment);
        if (parse !== null && parse.tags.some(t => FILEOVERVIEW_COMMENT_MARKERS.has(t.tagName))) {
          // Report a warning; this should not break compilation in third party code.
          reportDiagnostic(
              diagnostics, context, message, comment.originalRange, ts.DiagnosticCategory.Warning);
        }
      }
    }

    return (sourceFile: ts.SourceFile) => {
      // TypeScript supports including some other file formats in compilation
      // (JS, JSON). Avoid adding comments to those.
      if (!sourceFile.fileName.match(/\.tsx?$/)) {
        return sourceFile;
      }

      const text = sourceFile.getFullText();

      let fileComments: ts.SynthesizedComment[] = [];
      const firstStatement = sourceFile.statements.length && sourceFile.statements[0] || null;

      const originalComments = ts.getLeadingCommentRanges(text, 0) || [];
      if (!firstStatement) {
        // In an empty source file, all comments are file-level comments.
        fileComments = synthesizeCommentRanges(sourceFile, originalComments);
      } else {
        // Search for the last comment split from the file with a \n\n. All comments before that are
        // considered fileoverview comments, all comments after that belong to the next
        // statement(s). If none found, comments remains empty, and the code below will insert a new
        // fileoverview comment.
        for (let i = originalComments.length - 1; i >= 0; i--) {
          const end = originalComments[i].end;
          if (!text.substring(end).startsWith('\n\n') &&
              !text.substring(end).startsWith('\r\n\r\n')) {
            continue;
          }
          // This comment is separated from the source file with a double break, marking it (and any
          // preceding comments) as a file-level comment. Split them off and attach them onto a
          // NotEmittedStatement, so that they do not get lost later on.
          const synthesizedComments = jsdoc.synthesizeLeadingComments(firstStatement);
          const notEmitted = ts.createNotEmittedStatement(sourceFile);
          // Modify the comments on the firstStatement in place by removing the file-level comments.
          fileComments = synthesizedComments.splice(0, i + 1);
          // Move the fileComments onto notEmitted.
          ts.setSyntheticLeadingComments(notEmitted, fileComments);
          sourceFile = updateSourceFileNode(
              sourceFile,
              ts.createNodeArray([notEmitted, firstStatement, ...sourceFile.statements.slice(1)]));
          break;
        }


        // Now walk every top level statement and escape/drop any @fileoverview comments found.
        // Closure ignores all @fileoverview comments but the last, so tsickle must make sure not to
        // emit duplicated ones.
        for (let i = 0; i < sourceFile.statements.length; i++) {
          const stmt = sourceFile.statements[i];
          // Accept the NotEmittedStatement inserted above.
          if (i === 0 && stmt.kind === ts.SyntaxKind.NotEmittedStatement) {
            continue;
          }
          const comments = jsdoc.synthesizeLeadingComments(stmt);
          checkNoFileoverviewComments(
              stmt, comments,
              `file comments must be at the top of the file, ` +
                  `separated from the file body by an empty line.`);
        }
      }

      // Closure Compiler considers the *last* comment with @fileoverview (or #externs or
      // @nocompile) that has not been attached to some other tree node to be the file overview
      // comment, and only applies @suppress tags from it. Google-internal tooling considers *any*
      // comment mentioning @fileoverview.
      let fileoverviewIdx = -1;
      let tags: jsdoc.Tag[] = [];
      for (let i = fileComments.length - 1; i >= 0; i--) {
        const parse = jsdoc.parseContents(fileComments[i].text);
        if (parse !== null && parse.tags.some(t => FILEOVERVIEW_COMMENT_MARKERS.has(t.tagName))) {
          fileoverviewIdx = i;
          tags = parse.tags;
          break;
        }
      }

      if (fileoverviewIdx !== -1) {
        checkNoFileoverviewComments(
            firstStatement || sourceFile, fileComments.slice(0, fileoverviewIdx),
            `duplicate file level comment`);
      }

      augmentFileoverviewComments(options, sourceFile, tags);
      const commentText = jsdoc.toStringWithoutStartEnd(tags);

      if (fileoverviewIdx < 0) {
        // No existing comment to merge with, just emit a new one.
        return addNewFileoverviewComment(sourceFile, commentText);
      }

      fileComments[fileoverviewIdx].text = commentText;
      // sf does not need to be updated, synthesized comments are mutable.
      return sourceFile;
    };
  };
}