check-trailing-comments.py 2.7 KB

1234567891011121314151617181920212223242526272829303132333435363738394041424344454647484950515253545556575859606162636465666768697071727374757677787980818283848586878889909192
  1. #!/usr/bin/env python3
  2. """Reject trailing // comments that push a line past the 80-column limit.
  3. clang-format (kernel style, ReflowComments: false) cannot move a trailing
  4. comment onto its own line, so when a short statement carries a long trailing
  5. comment it either splits the statement or leaves the line over-length. Our
  6. convention is to instead put the comment on its own line(s) ABOVE the
  7. statement, separated by a blank line. This check enforces that: a line wider
  8. than the limit whose overflow comes from a trailing // comment is an error.
  9. Standalone comment lines (// in column 0..) are left alone -- the kernel style
  10. does not reflow comments, and those are not what triggers the bad split.
  11. """
  12. import sys
  13. LIMIT = 80
  14. TAB = 8
  15. def visual_width(s):
  16. """Width of a line with tabs expanded to the next multiple of TAB."""
  17. col = 0
  18. for ch in s:
  19. if ch == "\t":
  20. col += TAB - (col % TAB)
  21. else:
  22. col += 1
  23. return col
  24. def trailing_comment_index(line):
  25. """Char index of the first real `//` comment, or None.
  26. Skips `//` that appears inside a string or char literal so that e.g.
  27. "http://example" is not mistaken for a comment.
  28. """
  29. quote = None
  30. i, n = 0, len(line)
  31. while i < n:
  32. ch = line[i]
  33. if quote:
  34. if ch == "\\":
  35. i += 2
  36. continue
  37. if ch == quote:
  38. quote = None
  39. i += 1
  40. continue
  41. if ch in "\"'":
  42. quote = ch
  43. i += 1
  44. continue
  45. if ch == "/" and i + 1 < n and line[i + 1] == "/":
  46. return i
  47. i += 1
  48. return None
  49. def violations(path):
  50. out = []
  51. with open(path, errors="replace") as f:
  52. for lineno, raw in enumerate(f, 1):
  53. line = raw.rstrip("\n")
  54. if visual_width(line) <= LIMIT:
  55. continue
  56. ci = trailing_comment_index(line)
  57. if ci is None:
  58. continue # over-length, but not because of a comment
  59. if line[:ci].strip() == "":
  60. continue # standalone comment line -> allowed
  61. out.append((lineno, visual_width(line)))
  62. return out
  63. def main(argv):
  64. files = argv[1:]
  65. bad = 0
  66. for path in files:
  67. for lineno, width in violations(path):
  68. bad += 1
  69. print(f"{path}:{lineno}: trailing comment exceeds "
  70. f"{LIMIT} columns (line is {width}); "
  71. f"move the comment onto its own line above the statement")
  72. if bad:
  73. print(f"\n{bad} trailing-comment violation(s). "
  74. f"See tools/check-trailing-comments.py.", file=sys.stderr)
  75. return 1
  76. return 0
  77. if __name__ == "__main__":
  78. sys.exit(main(sys.argv))