Source code for ppbcc.code_complexity.loc
"""Line-based size metrics (LOC/SLOC/comment/blank lines).
The classification is derived from the token stream so that multi-line
comments, comments behind code and string literals containing ``//`` are all
handled correctly:
* **LOC** -- total number of physical lines.
* **SLOC** -- lines containing at least one code token (a line with code
*and* a trailing comment counts as source, not comment).
* **comment lines** -- lines whose only content is comment text.
* **blank lines** -- lines with neither code nor comments.
"""
from __future__ import annotations
from dataclasses import dataclass
from .tokenizer import Token, TokenKind
[docs]
@dataclass(frozen=True)
class LineMetrics:
"""Line counts of one source file.
Attributes:
loc: Total number of physical lines (Lines of Code).
sloc: Number of lines containing code (Source Lines of Code).
comment_lines: Number of comment-only lines.
blank_lines: Number of lines without code or comments.
"""
loc: int
sloc: int
comment_lines: int
blank_lines: int
[docs]
def as_dict(self) -> dict[str, int]:
"""Serialises the line counts.
Returns:
Mapping from metric name (matching the CSV column names) to its
value, in a stable order.
"""
return {
"loc": self.loc,
"sloc": self.sloc,
"comment_lines": self.comment_lines,
"blank_lines": self.blank_lines,
}
[docs]
def combine(self, other: "LineMetrics") -> "LineMetrics":
"""Adds another file's line counts (used for aggregation).
Args:
other: Line counts to add.
Returns:
A new :class:`LineMetrics` with the summed counts.
"""
return LineMetrics(
loc=self.loc + other.loc,
sloc=self.sloc + other.sloc,
comment_lines=self.comment_lines + other.comment_lines,
blank_lines=self.blank_lines + other.blank_lines,
)
[docs]
def count_lines(code: str, tokens: list[Token]) -> LineMetrics:
"""Computes the line metrics of one source file.
Args:
code: Raw source text.
tokens: Token stream of the same text, as produced by
:func:`ppbcc.code_complexity.tokenizer.tokenize`.
Returns:
The :class:`LineMetrics` of the file.
"""
if not code:
return LineMetrics(loc=0, sloc=0, comment_lines=0, blank_lines=0)
total = code.count("\n") + (0 if code.endswith("\n") else 1)
code_lines: set[int] = set()
comment_lines: set[int] = set()
for token in tokens:
target = comment_lines if token.kind is TokenKind.COMMENT else code_lines
target.update(range(token.line, token.end_line + 1))
comment_only = comment_lines - code_lines
blank = total - len(code_lines | comment_lines)
return LineMetrics(
loc=total,
sloc=len(code_lines),
comment_lines=len(comment_only),
blank_lines=blank,
)