check_header_guards.py 3.4 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113
  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_path = str(path).upper().replace("/", "_").replace(".", "_")
  46. guard = f"CARBON_{guard_path}_"
  47. ifndef = find_guard(lines, "#ifndef ([A-Z0-9_]+_H_)", False)
  48. define = find_guard(lines, "#define ([A-Z0-9_]+_H_)", False)
  49. endif = find_guard(lines, "#endif(?: // ([A-Z0-9_]+_H_))?", True)
  50. if ifndef is None or define is None or endif is None:
  51. print(f"Incomplete header guard in {path}:", file=sys.stderr)
  52. if ifndef is None:
  53. print(f"- Missing `#ifndef {guard}`", file=sys.stderr)
  54. if define is None:
  55. print(f"- Missing `#define {guard}`", file=sys.stderr)
  56. if endif is None:
  57. print(f"- Missing `#endif // {guard}`", file=sys.stderr)
  58. return True
  59. if ifndef.line + 1 != define.line:
  60. print(
  61. f"Non-consecutive header guard in {path}: "
  62. f"#ifndef on line {ifndef.line + 1}, "
  63. f"#define on line {define.line + 1}.",
  64. file=sys.stderr,
  65. )
  66. return True
  67. if endif.line != len(lines) - 1:
  68. print(
  69. f"Misordered header guard in {path}: #endif on line {endif.line}, "
  70. f"should be on last line ({len(lines) - 1}).",
  71. file=sys.stderr,
  72. )
  73. return True
  74. if guard != ifndef.guard or guard != define.guard or guard != endif.guard:
  75. print(f"Fixing header guard in {path} to {guard}:", file=sys.stderr)
  76. maybe_replace(lines, ifndef, "#ifndef", guard)
  77. maybe_replace(lines, define, "#define", guard)
  78. maybe_replace(lines, endif, "#endif //", guard)
  79. with path.open("w") as f:
  80. f.writelines(lines)
  81. return True
  82. return False
  83. def main() -> None:
  84. has_errors = False
  85. for arg in sys.argv[1:]:
  86. if check_path(Path(arg)):
  87. has_errors = True
  88. if has_errors:
  89. exit(1)
  90. if __name__ == "__main__":
  91. main()