raw_metrics.py 3.6 KB

123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109
  1. # Licensed under the GPL: https://www.gnu.org/licenses/old-licenses/gpl-2.0.html
  2. # For details: https://github.com/pylint-dev/pylint/blob/main/LICENSE
  3. # Copyright (c) https://github.com/pylint-dev/pylint/blob/main/CONTRIBUTORS.txt
  4. from __future__ import annotations
  5. import tokenize
  6. from typing import TYPE_CHECKING, Any, Literal
  7. from pylint.checkers import BaseTokenChecker
  8. from pylint.reporters.ureports.nodes import Paragraph, Section, Table, Text
  9. from pylint.utils import LinterStats, diff_string
  10. if TYPE_CHECKING:
  11. from pylint.lint import PyLinter
  12. def report_raw_stats(
  13. sect: Section,
  14. stats: LinterStats,
  15. old_stats: LinterStats | None,
  16. ) -> None:
  17. """Calculate percentage of code / doc / comment / empty."""
  18. total_lines = stats.code_type_count["total"]
  19. sect.insert(0, Paragraph([Text(f"{total_lines} lines have been analyzed\n")]))
  20. lines = ["type", "number", "%", "previous", "difference"]
  21. for node_type in ("code", "docstring", "comment", "empty"):
  22. total = stats.code_type_count[node_type]
  23. percent = float(total * 100) / total_lines if total_lines else None
  24. old = old_stats.code_type_count[node_type] if old_stats else None
  25. diff_str = diff_string(old, total) if old else None
  26. lines += [
  27. node_type,
  28. str(total),
  29. f"{percent:.2f}" if percent is not None else "NC",
  30. str(old) if old else "NC",
  31. diff_str if diff_str else "NC",
  32. ]
  33. sect.append(Table(children=lines, cols=5, rheaders=1))
  34. class RawMetricsChecker(BaseTokenChecker):
  35. """Checker that provides raw metrics instead of checking anything.
  36. Provides:
  37. * total number of lines
  38. * total number of code lines
  39. * total number of docstring lines
  40. * total number of comments lines
  41. * total number of empty lines
  42. """
  43. # configuration section name
  44. name = "metrics"
  45. # configuration options
  46. options = ()
  47. # messages
  48. msgs: Any = {}
  49. # reports
  50. reports = (("RP0701", "Raw metrics", report_raw_stats),)
  51. def open(self) -> None:
  52. """Init statistics."""
  53. self.linter.stats.reset_code_count()
  54. def process_tokens(self, tokens: list[tokenize.TokenInfo]) -> None:
  55. """Update stats."""
  56. i = 0
  57. tokens = list(tokens)
  58. while i < len(tokens):
  59. i, lines_number, line_type = get_type(tokens, i)
  60. self.linter.stats.code_type_count["total"] += lines_number
  61. self.linter.stats.code_type_count[line_type] += lines_number
  62. JUNK = (tokenize.NL, tokenize.INDENT, tokenize.NEWLINE, tokenize.ENDMARKER)
  63. def get_type(
  64. tokens: list[tokenize.TokenInfo], start_index: int
  65. ) -> tuple[int, int, Literal["code", "docstring", "comment", "empty"]]:
  66. """Return the line type : docstring, comment, code, empty."""
  67. i = start_index
  68. start = tokens[i][2]
  69. pos = start
  70. line_type = None
  71. while i < len(tokens) and tokens[i][2][0] == start[0]:
  72. tok_type = tokens[i][0]
  73. pos = tokens[i][3]
  74. if line_type is None:
  75. if tok_type == tokenize.STRING:
  76. line_type = "docstring"
  77. elif tok_type == tokenize.COMMENT:
  78. line_type = "comment"
  79. elif tok_type in JUNK:
  80. pass
  81. else:
  82. line_type = "code"
  83. i += 1
  84. if line_type is None:
  85. line_type = "empty"
  86. elif i < len(tokens) and tokens[i][0] == tokenize.NEWLINE:
  87. i += 1
  88. # Mypy fails to infer the literal of line_type
  89. return i, pos[0] - start[0] + 1, line_type # type: ignore[return-value]
  90. def register(linter: PyLinter) -> None:
  91. linter.register_checker(RawMetricsChecker(linter))