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