check_header_guards.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112
  1. #!/usr/bin/env python3
  2. """Checks for missing or incorrect header guards."""
  3. __copyright__ = """
  4. Part of the Carbon Language project, under the Apache License v2.0 with LLVM
  5. Exceptions. See /LICENSE for license information.
  6. SPDX-License-Identifier: Apache-2.0 WITH LLVM-exception
  7. """
  8. from pathlib import Path
  9. import re
  10. import sys
  11. from typing import Iterable, List, NamedTuple, Optional
  12. class Guard(NamedTuple):
  13. """A guard line in a file."""
  14. line: int
  15. guard: str
  16. def find_guard(
  17. lines: List[str], pattern: str, from_end: bool
  18. ) -> Optional[Guard]:
  19. """Searches the lines for something matching the pattern."""
  20. lines_range: Iterable[str] = lines
  21. if from_end:
  22. lines_range = reversed(lines)
  23. for index, line in enumerate(lines_range):
  24. m = re.match(pattern, line)
  25. if m:
  26. if from_end:
  27. index = len(lines) - index - 1
  28. return Guard(index, m[1])
  29. return None
  30. def maybe_replace(
  31. lines: List[str], old_guard: Guard, guard_prefix: str, guard: str
  32. ) -> None:
  33. """Replaces a header guard in the file if needed."""
  34. if guard != old_guard.guard:
  35. line = lines[old_guard.line].rstrip("\n")
  36. print(f"- Replacing line {old_guard.line}: `{line}`", file=sys.stderr)
  37. lines[old_guard.line] = f"{guard_prefix} {guard}\n"
  38. def check_path(path: Path) -> bool:
  39. """Checks the path for header guard issues."""
  40. if path.suffix != ".h":
  41. print(f"Not a header: {path}", file=sys.stderr)
  42. return True
  43. with path.open() as f:
  44. lines = f.readlines()
  45. guard = str(path).upper().replace("/", "_").replace(".", "_") + "_"
  46. ifndef = find_guard(lines, "#ifndef ([A-Z_]+_H_)", False)
  47. define = find_guard(lines, "#define ([A-Z_]+_H_)", False)
  48. endif = find_guard(lines, "#endif(?: // ([A-Z_]+_H_))?", True)
  49. if ifndef is None or define is None or endif is None:
  50. print(f"Incomplete header guard in {path}:", file=sys.stderr)
  51. if ifndef is None:
  52. print(f"- Missing `#ifndef {guard}`", file=sys.stderr)
  53. if define is None:
  54. print(f"- Missing `#define {guard}`", file=sys.stderr)
  55. if endif is None:
  56. print(f"- Missing `#endif // {guard}`", file=sys.stderr)
  57. return True
  58. if ifndef.line + 1 != define.line:
  59. print(
  60. f"Non-consecutive header guard in {path}: "
  61. f"#ifndef on line {ifndef.line + 1}, "
  62. f"#define on line {define.line + 1}.",
  63. file=sys.stderr,
  64. )
  65. return True
  66. if endif.line != len(lines) - 1:
  67. print(
  68. f"Misordered header guard in {path}: #endif on line {endif.line}, "
  69. f"should be on last line ({len(lines) - 1}).",
  70. file=sys.stderr,
  71. )
  72. return True
  73. if guard != ifndef.guard or guard != define.guard or guard != endif.guard:
  74. print(f"Fixing header guard in {path} to {guard}:", file=sys.stderr)
  75. maybe_replace(lines, ifndef, "#ifndef", guard)
  76. maybe_replace(lines, define, "#define", guard)
  77. maybe_replace(lines, endif, "#endif //", guard)
  78. with path.open("w") as f:
  79. f.writelines(lines)
  80. return True
  81. return False
  82. def main() -> None:
  83. has_errors = False
  84. for arg in sys.argv[1:]:
  85. if check_path(Path(arg)):
  86. has_errors = True
  87. if has_errors:
  88. exit(1)
  89. if __name__ == "__main__":
  90. main()