xref: /aosp_15_r20/external/pigweed/pw_presubmit/py/pw_presubmit/format/cpp.py (revision 61c4878ac05f98d0ceed94b57d316916de578985)
1# Copyright 2024 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Code formatter plugin for C/C++."""
15
16from pathlib import Path
17from typing import Final, Iterable, Iterator, Sequence
18
19from pw_presubmit.format.core import (
20    FileFormatter,
21    FormattedFileContents,
22    FormatFixStatus,
23)
24
25
26class ClangFormatFormatter(FileFormatter):
27    """A formatter that runs `clang-format` on files."""
28
29    DEFAULT_FLAGS: Final[Sequence[str]] = ('--style=file',)
30
31    def __init__(self, tool_flags: Sequence[str] = DEFAULT_FLAGS, **kwargs):
32        super().__init__(**kwargs)
33        self.clang_format_flags = list(tool_flags)
34
35    def format_file_in_memory(
36        self, file_path: Path, file_contents: bytes
37    ) -> FormattedFileContents:
38        """Uses ``clang-format`` to check the formatting of the requested file.
39
40        The file at ``file_path`` is NOT modified by this check.
41
42        Returns:
43            A populated
44            :py:class:`pw_presubmit.format.core.FormattedFileContents` that
45            contains either the result of formatting the file, or an error
46            message.
47        """
48        proc = self.run_tool(
49            'clang-format',
50            self.clang_format_flags + [file_path],
51        )
52        return FormattedFileContents(
53            ok=proc.returncode == 0,
54            formatted_file_contents=proc.stdout,
55            error_message=proc.stderr.decode()
56            if proc.returncode != 0
57            else None,
58        )
59
60    def format_file(self, file_path: Path) -> FormatFixStatus:
61        """Formats the provided file in-place using ``clang-format``.
62
63        Returns:
64            A FormatFixStatus that contains relevant errors/warnings.
65        """
66        self.format_files([file_path])
67        # `clang-format` doesn't emit errors, and will always try its best to
68        # format malformed files.
69        return FormatFixStatus(ok=True, error_message=None)
70
71    def format_files(
72        self, paths: Iterable[Path], keep_warnings: bool = True
73    ) -> Iterator[tuple[Path, FormatFixStatus]]:
74        """Uses ``clang-format`` to format the specified files in-place.
75
76        Returns:
77            An iterator of ``Path`` and
78            :py:class:`pw_presubmit.format.core.FormatFixStatus` pairs for each
79            file that was not successfully formatted. If ``keep_warnings`` is
80            ``True``, any successful format operations with warnings will also
81            be returned.
82        """
83        self.run_tool(
84            'clang-format',
85            ['-i'] + self.clang_format_flags + list(paths),
86            check=True,
87        )
88
89        # `clang-format` doesn't emit errors, and will always try its best to
90        # format malformed files. For that reason, no errors are yielded.
91        yield from []
92