text: #!/usr/bin/env python3
  # Copyright (c) Meta Platforms, Inc. and affiliates.
  #
  # This source code is licensed under the MIT license found in the
  # LICENSE file in the root directory of this source tree.
  
  import ast
  import itertools
  from pathlib import Path
  from typing import Callable, List, NamedTuple, Type
  
  
  class Error(NamedTuple):
      lineno: int
      col: int
      message: str
      type: Type
  
  
  def should_check(filename):
      # Getting options for plugins in flake8 is a bit of a hassle so we just hardcode
      # our conventions.
  
      path = Path(filename)
      if path.parent.name not in ("tests", "experimental", "flake8_plugins"):
          return True
      with path.open() as fd:
          for line in itertools.islice(fd, 5):
              if line == "# check-docstrings\n":
                  return True
      return False
  
  
  class DocstringChecker:
      """
      A flake8 plug-in that makes sure all public functions have a docstring
      """
  
      name: str = "docstring checker"
      version: str = "1.0.0"
      fikename: str
      tree: ast.Module
  
      def __init__(self, tree, filename):
          self.filename = filename
          self.tree = tree
  
      def run(self):
          if not should_check(self.filename):
              return
          visitor = DocstringCheckerVisitor()
          visitor.visit(self.tree)
          yield from visitor.errors
  
  
  def is_copy_doc_call(c):
      """Tries to guess if this is a call to the ``copy_doc`` decorator. This is
      a purely syntactic check so if the decorator was aliased as another name]
      or wrapped in another function we will fail.
      """
      if not isinstance(c, ast.Call):
          return False
      func = c.func
      if isinstance(func, ast.Attribute):
          name = func.attr
      elif isinstance(func, ast.Name):
          name = func.id
      else:
          return False
      return name == "copy_doc"
  
  
  class DocstringCheckerVisitor(ast.NodeVisitor):
      errors: List[Error]
  
      def __init__(self) -> None:
          self.errors = []
  
      def visit_FunctionDef(self, node: ast.FunctionDef) -> None:
          self.check_A000(node)
          self.generic_visit(node)
  
      def visit_ClassDef(self, node: ast.ClassDef) -> None:
          self.check_A000(node)
          self.generic_visit(node)
  
      def visit_AsyncFunctionDef(self, node: ast.ClassDef) -> None:
          self.check_A000(node)
          self.generic_visit(node)
  
      def check_A000(self, node: ast.AST) -> None:
          if node.name.startswith("_"):
              return
          docstring = ast.get_docstring(node)
          if docstring is None and not any(
              is_copy_doc_call(dec) for dec in node.decorator_list
          ):
              self.errors.append(A000(node))
  
  
  # Error classes E, C, W and F are used by flake8. T by mypy and B by bugbear
  def new_error(errorid: str, msg: str) -> Callable[[ast.AST], Error]:
      full_message = f"{errorid} {msg}"
  
      def mk_error(node: ast.AST) -> Error:
          return Error(
              lineno=node.lineno,
              col=node.col_offset,
              message=full_message,
              type=DocstringChecker,
          )
  
      mk_error.__name__ = errorid
      return mk_error
  
  
  A000 = new_error(
      "A000",
      "Missing docstring. All public classes, functions and methods should have "
      "docstrings (cf https://fburl.com/wiki/wbcrsoeo).",
  )
  