Skip to content

Commit

Permalink
feat: prettiers the output to be more inline with clang-tidy (#2)
Browse files Browse the repository at this point in the history
Based on #1, originally draft by @ArchieAtkinson

close #1

---------

Co-authored-by: Archie Atkinson <[email protected]>
  • Loading branch information
lljbash and ArchieAtkinson authored Apr 29, 2024
1 parent 636c50e commit 4cbc11f
Show file tree
Hide file tree
Showing 3 changed files with 335 additions and 84 deletions.
50 changes: 33 additions & 17 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -11,29 +11,31 @@ Unfortunately, there seems to be no plan within LLVM to accelerate the standalon
## Comparison with clang-tidy

**Pros:**

- clangd-tidy is significantly faster than clang-tidy (over 10x in my experience).
- clangd-tidy can check header files individually, even if they are not included in the compilation database.
- clangd-tidy groups diagnostics by files -- no more duplicated diagnostics from the same header!
- clangd-tidy supports [`.clangd` configuration files](https://clangd.llvm.org/config), offering features not supported by clang-tidy.
- Example: Removing unknown compiler flags from the compilation database.
```yaml
CompileFlags:
Remove: -fabi*
```
- Example: Adding IWYU include checks.
```yaml
Diagnostics:
# Available in clangd-14
UnusedIncludes: Strict
# Require clangd-17
MissingIncludes: Strict
```
- Example: Removing unknown compiler flags from the compilation database.
```yaml
CompileFlags:
Remove: -fabi*
```
- Example: Adding IWYU include checks.
```yaml
Diagnostics:
# Available in clangd-14
UnusedIncludes: Strict
# Require clangd-17
MissingIncludes: Strict
```
- Refer to [Usage](#usage) for more features.
**Cons:**
- clangd-tidy lacks support for the `--fix` option. (Consider using code actions provided by your editor if you have clangd properly configured, as clangd-tidy is primarily designed for speeding up CI checks.)
- clangd-tidy silently disables [several](https://searchfox.org/llvm/rev/cb7bda2ace81226c5b33165411dd0316f93fa57e/clang-tools-extra/clangd/TidyProvider.cpp#199-227) checks not supported by clangd.
- Diagnostics generated by clangd-tidy are less aesthetically pleasing than clang-tidy.
- Diagnostics generated by clangd-tidy might be marginally less aesthetically pleasing compared to clang-tidy.

## Prerequisites

Expand All @@ -47,7 +49,9 @@ Unfortunately, there seems to be no plan within LLVM to accelerate the standalon
usage: clangd-tidy [-h] [-p COMPILE_COMMANDS_DIR] [-j JOBS] [-o OUTPUT]
[--clangd-executable CLANGD_EXECUTABLE]
[--allow-extensions ALLOW_EXTENSIONS]
[--fail-on-severity SEVERITY] [--tqdm] [-v]
[--fail-on-severity SEVERITY] [--tqdm] [--github]
[--git-root GIT_ROOT] [-c] [--context CONTEXT]
[--color {auto,always,never}] [-v]
filename [filename ...]

Run clangd with clang-tidy and output diagnostics. This aims to serve as a
Expand All @@ -59,6 +63,7 @@ positional arguments:

options:
-h, --help show this help message and exit
-V, --version show program's version number and exit
-p COMPILE_COMMANDS_DIR, --compile-commands-dir COMPILE_COMMANDS_DIR
Specify a path to look for compile_commands.json. If
the path is invalid, clangd will look in the current
Expand All @@ -77,11 +82,18 @@ options:
On which severity of diagnostics this program should
exit with a non-zero status. Candidates: error, warn,
info, hint. [default: hint]
--tqdm Show a progress bar (require tqdm).
--tqdm Show a progress bar (tqdm required).
--github Append workflow commands for GitHub Actions to output.
--git-root GIT_ROOT Root directory of the git repository. Only works with
--github. [default: current directory]
-v, --verbose Print verbose output from clangd.
-c, --compact Print compact diagnostics (legacy).
--context CONTEXT Number of additional lines to display on both sides of
each diagnostic. This option is ineffective with
--compact. [default: 2]
--color {auto,always,never}
Colorize the output. This option is ineffective with
--compact. [default: auto]
-v, --verbose Show verbose output from clangd.

Find more information on https://github.com/lljbash/clangd-tidy.
```
Expand All @@ -91,3 +103,7 @@ Find more information on https://github.com/lljbash/clangd-tidy.
Special thanks to [@yeger00](https://github.com/yeger00) for his [pylspclient](https://github.com/yeger00/pylspclient).
A big shoutout to [clangd](https://clangd.llvm.org/) and [clang-tidy](https://clang.llvm.org/extra/clang-tidy/) for their great work!
Claps to [@ArchieAtkinson](https://github.com/ArchieAtkinson) for his artistic flair in the fancy diagnostic formatter.
Contributions are welcome! Feel free to open an issue or a pull request.
119 changes: 52 additions & 67 deletions clangd-tidy
Original file line number Diff line number Diff line change
Expand Up @@ -8,11 +8,14 @@ import sys
import threading
from typing import IO, Set, TextIO

import diagnostic_formatter
from pylspclient.json_rpc_endpoint import JsonRpcEndpoint
from pylspclient.lsp_endpoint import LspEndpoint
from pylspclient.lsp_client import LspClient
from pylspclient.lsp_structs import TextDocumentItem, LANGUAGE_IDENTIFIER

__version__ = "0.2.0"


class ReadPipe(threading.Thread):
def __init__(self, pipe: IO[bytes], out: TextIO):
Expand Down Expand Up @@ -58,25 +61,17 @@ def _uri_file(uri: str):
return uri[7:]


def _is_output_supports_color(output: TextIO):
return hasattr(output, "isatty") and output.isatty()


class DiagnosticCollector:
SEVERITY = {
1: "Error",
2: "Warning",
3: "Information",
4: "Hint",
}
SEVERITY_INT = {
"error": 1,
"warn": 2,
"info": 3,
"hint": 4,
}
SEVERITY_GITHUB = {
1: "error",
2: "warning",
3: "notice",
4: "notice",
}

def __init__(self):
self.diagnostics = {}
Expand Down Expand Up @@ -110,56 +105,10 @@ class DiagnosticCollector:
return True
return False

def fancy_diagnostics(self) -> str:
fancy_output = ""
for file, diagnostics in sorted(self.diagnostics.items()):
if len(diagnostics) == 0:
continue
fancy_output += "----- {} -----\n\n".format(os.path.relpath(file))
for diagnostic in diagnostics:
source = diagnostic.get("source", None)
severity = diagnostic.get("severity", None)
code = diagnostic.get("code", None)
extra_info = "{}{}{}".format(
f" {source}" if source else "",
f" {self.SEVERITY[severity]}" if severity else "",
f" [{code}]" if code else "",
)
line = diagnostic["range"]["start"]["line"] + 1
col = diagnostic["range"]["start"]["character"] + 1
message = diagnostic["message"]
if source is None and code is None:
continue
fancy_output += f"- line {line}, col {col}:{extra_info}\n{message}\n\n"
fancy_output += "\n"
return fancy_output

def workflow_commands_for_github_actions(self, git_root: str) -> str:
commands = "::group::{workflow commands}\n"
for file, diagnostics in sorted(self.diagnostics.items()):
if len(diagnostics) == 0:
continue
for diagnostic in diagnostics:
source = diagnostic.get("source", None)
severity = diagnostic.get("severity", None)
code = diagnostic.get("code", None)
extra_info = "{}{}{}".format(
f"{source}" if source else "",
f" {self.SEVERITY[severity]}" if severity else "",
f" [{code}]" if code else "",
)
line = diagnostic["range"]["start"]["line"] + 1
end_line = diagnostic["range"]["end"]["line"] + 1
col = diagnostic["range"]["start"]["character"] + 1
end_col = diagnostic["range"]["end"]["character"] + 1
message = diagnostic["message"]
if source is None and code is None:
continue
command = self.SEVERITY_GITHUB[severity]
rel_file = os.path.relpath(file, git_root)
commands += f"::{command} file={rel_file},line={line},endLine={end_line},col={col},endCol={end_col},title={extra_info}::{message}\n"
commands += "::endgroup::"
return commands
def format_diagnostics(
self, formatter: diagnostic_formatter.DiagnosticFormatter
) -> str:
return formatter.format(sorted(self.diagnostics.items())).rstrip()


if __name__ == "__main__":
Expand All @@ -181,6 +130,9 @@ if __name__ == "__main__":
description="Run clangd with clang-tidy and output diagnostics. This aims to serve as a faster alternative to clang-tidy.",
epilog="Find more information on https://github.com/lljbash/clangd-tidy.",
)
parser.add_argument(
"-V", "--version", action="version", version=f"%(prog)s {__version__}"
)
parser.add_argument(
"-p",
"--compile-commands-dir",
Expand Down Expand Up @@ -219,7 +171,7 @@ if __name__ == "__main__":
help=f"On which severity of diagnostics this program should exit with a non-zero status. Candidates: {', '.join(DiagnosticCollector.SEVERITY_INT)}. [default: hint]",
)
parser.add_argument(
"--tqdm", action="store_true", help="Show a progress bar (require tqdm)."
"--tqdm", action="store_true", help="Show a progress bar (tqdm required)."
)
parser.add_argument(
"--github",
Expand All @@ -232,7 +184,25 @@ if __name__ == "__main__":
help="Root directory of the git repository. Only works with --github. [default: current directory]",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="Print verbose output from clangd."
"-c",
"--compact",
action="store_true",
help="Print compact diagnostics (legacy).",
)
parser.add_argument(
"--context",
type=int,
default=2,
help="Number of additional lines to display on both sides of each diagnostic. This option is ineffective with --compact. [default: 2]",
)
parser.add_argument(
"--color",
choices=["auto", "always", "never"],
default="auto",
help="Colorize the output. This option is ineffective with --compact. [default: auto]",
)
parser.add_argument(
"-v", "--verbose", action="store_true", help="Show verbose output from clangd."
)
parser.add_argument(
"filename",
Expand Down Expand Up @@ -328,11 +298,26 @@ if __name__ == "__main__":
if read_pipe.is_alive():
read_pipe.join()

diagnostics = collector.fancy_diagnostics().strip()
print(diagnostics, file=args.output)
formatter = (
diagnostic_formatter.FancyDiagnosticFormatter(
extra_context=args.context,
enable_color=(
_is_output_supports_color(args.output)
if args.color == "auto"
else args.color == "always"
),
)
if not args.compact
else diagnostic_formatter.CompactDiagnosticFormatter()
)
print(collector.format_diagnostics(formatter), file=args.output)
if args.github:
print(
collector.workflow_commands_for_github_actions(args.git_root).strip(),
collector.format_diagnostics(
diagnostic_formatter.GithubActionWorkflowCommandDiagnosticFormatter(
args.git_root
)
),
file=args.output,
)
if collector.check_failed(args.fail_on_severity):
Expand Down
Loading

0 comments on commit 4cbc11f

Please sign in to comment.