check_header_guards.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114
  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 collections.abc import Iterable
  9. from pathlib import Path
  10. import re
  11. import sys
  12. from typing import NamedTuple, Optional
  13. class Guard(NamedTuple):
  14. """A guard line in a file."""
  15. line: int
  16. guard: str
  17. def find_guard(
  18. lines: list[str], pattern: str, from_end: bool
  19. ) -> Optional[Guard]:
  20. """Searches the lines for something matching the pattern."""
  21. lines_range: Iterable[str] = lines
  22. if from_end:
  23. lines_range = reversed(lines)
  24. for index, line in enumerate(lines_range):
  25. m = re.match(pattern, line)
  26. if m:
  27. if from_end:
  28. index = len(lines) - index - 1
  29. return Guard(index, m[1])
  30. return None
  31. def maybe_replace(
  32. lines: list[str], old_guard: Guard, guard_prefix: str, guard: str
  33. ) -> None:
  34. """Replaces a header guard in the file if needed."""
  35. if guard != old_guard.guard:
  36. line = lines[old_guard.line].rstrip("\n")
  37. print(f"- Replacing line {old_guard.line}: `{line}`", file=sys.stderr)
  38. lines[old_guard.line] = f"{guard_prefix} {guard}\n"
  39. def check_path(path: Path) -> bool:
  40. """Checks the path for header guard issues."""
  41. if path.suffix != ".h":
  42. print(f"Not a header: {path}", file=sys.stderr)
  43. return True
  44. with path.open() as f:
  45. lines = f.readlines()
  46. guard_path = str(path).upper().replace("/", "_").replace(".", "_")
  47. guard = f"CARBON_{guard_path}_"
  48. ifndef = find_guard(lines, "#ifndef ([A-Z0-9_]+_H_)", False)
  49. define = find_guard(lines, "#define ([A-Z0-9_]+_H_)", False)
  50. endif = find_guard(lines, "#endif(?: // ([A-Z0-9_]+_H_))?", True)
  51. if ifndef is None or define is None or endif is None:
  52. print(f"Incomplete header guard in {path}:", file=sys.stderr)
  53. if ifndef is None:
  54. print(f"- Missing `#ifndef {guard}`", file=sys.stderr)
  55. if define is None:
  56. print(f"- Missing `#define {guard}`", file=sys.stderr)
  57. if endif is None:
  58. print(f"- Missing `#endif // {guard}`", file=sys.stderr)
  59. return True
  60. if ifndef.line + 1 != define.line:
  61. print(
  62. f"Non-consecutive header guard in {path}: "
  63. f"#ifndef on line {ifndef.line + 1}, "
  64. f"#define on line {define.line + 1}.",
  65. file=sys.stderr,
  66. )
  67. return True
  68. if endif.line != len(lines) - 1:
  69. print(
  70. f"Misordered header guard in {path}: #endif on line {endif.line}, "
  71. f"should be on last line ({len(lines) - 1}).",
  72. file=sys.stderr,
  73. )
  74. return True
  75. if guard != ifndef.guard or guard != define.guard or guard != endif.guard:
  76. print(f"Fixing header guard in {path} to {guard}:", file=sys.stderr)
  77. maybe_replace(lines, ifndef, "#ifndef", guard)
  78. maybe_replace(lines, define, "#define", guard)
  79. maybe_replace(lines, endif, "#endif //", guard)
  80. with path.open("w") as f:
  81. f.writelines(lines)
  82. return True
  83. return False
  84. def main() -> None:
  85. has_errors = False
  86. for arg in sys.argv[1:]:
  87. if check_path(Path(arg)):
  88. has_errors = True
  89. if has_errors:
  90. exit(1)
  91. if __name__ == "__main__":
  92. main()