Add presubmit check for useless forward declarations.

Borrowed from Chromium.

Change-Id: I421ef1cc20f28e03b00ed91a765a64922394c76b
Reviewed-on: https://pdfium-review.googlesource.com/c/pdfium/+/82110
Reviewed-by: Tom Sepez <tsepez@chromium.org>
Commit-Queue: Lei Zhang <thestig@chromium.org>
diff --git a/PRESUBMIT.py b/PRESUBMIT.py
index 89c19b5..3eea4fe 100644
--- a/PRESUBMIT.py
+++ b/PRESUBMIT.py
@@ -293,6 +293,7 @@
       tests_added.append(path)
   return results
 
+
 def _CheckPNGFormat(input_api, output_api):
   """Checks that .png files have a format that will be considered valid by our
   test runners. If a file ends with .png, then it must be of the form
@@ -309,6 +310,54 @@
         'PNG file %s does not have the correct format' % f.LocalPath()))
   return results
 
+
+def _CheckUselessForwardDeclarations(input_api, output_api):
+  """Checks that added or removed lines in non third party affected
+     header files do not lead to new useless class or struct forward
+     declaration.
+  """
+  results = []
+  class_pattern = input_api.re.compile(r'^class\s+(\w+);$',
+                                       input_api.re.MULTILINE)
+  struct_pattern = input_api.re.compile(r'^struct\s+(\w+);$',
+                                        input_api.re.MULTILINE)
+  for f in input_api.AffectedFiles(include_deletes=False):
+    if f.LocalPath().startswith('third_party'):
+      continue
+
+    if not f.LocalPath().endswith('.h'):
+      continue
+
+    contents = input_api.ReadFile(f)
+    fwd_decls = input_api.re.findall(class_pattern, contents)
+    fwd_decls.extend(input_api.re.findall(struct_pattern, contents))
+
+    useless_fwd_decls = []
+    for decl in fwd_decls:
+      count = sum(
+          1
+          for _ in input_api.re.finditer(r'\b%s\b' %
+                                         input_api.re.escape(decl), contents))
+      if count == 1:
+        useless_fwd_decls.append(decl)
+
+    if not useless_fwd_decls:
+      continue
+
+    for line in f.GenerateScmDiff().splitlines():
+      if (line.startswith('-') and not line.startswith('--') or
+          line.startswith('+') and not line.startswith('++')):
+        for decl in useless_fwd_decls:
+          if input_api.re.search(r'\b%s\b' % decl, line[1:]):
+            results.append(
+                output_api.PresubmitPromptWarning(
+                    '%s: %s forward declaration is no longer needed' %
+                    (f.LocalPath(), decl)))
+            useless_fwd_decls.remove(decl)
+
+  return results
+
+
 def CheckChangeOnUpload(input_api, output_api):
   results = []
   results.extend(_CheckUnwantedDependencies(input_api, output_api))
@@ -320,6 +369,7 @@
   results.extend(_CheckIncludeOrder(input_api, output_api))
   results.extend(_CheckTestDuplicates(input_api, output_api))
   results.extend(_CheckPNGFormat(input_api, output_api))
+  results.extend(_CheckUselessForwardDeclarations(input_api, output_api))
 
   author = input_api.change.author_email
   if author and author not in _KNOWN_ROBOTS: