From: Sasha Levin <sashal@kernel.org>
To: dwaipayanray1@gmail.com, lukas.bulwahn@gmail.com
Cc: joe@perches.com, corbet@lwn.net, skhan@linuxfoundation.org,
apw@canonical.com, workflows@vger.kernel.org,
linux-doc@vger.kernel.org, linux-kernel@vger.kernel.org,
Sasha Levin <sashal@kernel.org>
Subject: [PATCH] checkpatch: add --json output mode
Date: Mon, 6 Apr 2026 13:00:39 -0400 [thread overview]
Message-ID: <20260406170039.4034716-1-sashal@kernel.org> (raw)
Add a --json flag to checkpatch.pl that emits structured JSON output,
making results machine-parseable for CI systems, IDE integrations, and
AI-assisted code review tools.
The JSON output includes per-file totals (errors, warnings, checks,
lines) and an array of individual issues with structured fields for
level, type, message, file path, and line number.
The --json flag is mutually exclusive with --terse and --emacs.
Normal text output behavior is completely unchanged when --json is
not specified.
Assisted-by: Claude:claude-opus-4-6
Signed-off-by: Sasha Levin <sashal@kernel.org>
---
Documentation/dev-tools/checkpatch.rst | 7 +++
scripts/checkpatch.pl | 86 ++++++++++++++++++++++++--
2 files changed, 87 insertions(+), 6 deletions(-)
diff --git a/Documentation/dev-tools/checkpatch.rst b/Documentation/dev-tools/checkpatch.rst
index dccede68698ca..17e5744d3dee6 100644
--- a/Documentation/dev-tools/checkpatch.rst
+++ b/Documentation/dev-tools/checkpatch.rst
@@ -64,6 +64,13 @@ Available options:
Output only one line per report.
+ - --json
+
+ Output results as a JSON object. The object includes total error, warning,
+ and check counts, plus an array of individual issues with structured fields
+ for level, type, message, file, and line number. Cannot be used with
+ --terse or --emacs.
+
- --showfile
Show the diffed file position instead of the input file position.
diff --git a/scripts/checkpatch.pl b/scripts/checkpatch.pl
index e56374662ff79..ed70753ba1afc 100755
--- a/scripts/checkpatch.pl
+++ b/scripts/checkpatch.pl
@@ -33,6 +33,7 @@ my $chk_patch = 1;
my $tst_only;
my $emacs = 0;
my $terse = 0;
+my $json = 0;
my $showfile = 0;
my $file = 0;
my $git = 0;
@@ -93,6 +94,7 @@ Options:
--patch treat FILE as patchfile (default)
--emacs emacs compile window format
--terse one line per report
+ --json output results as JSON
--showfile emit diffed file position, not input file position
-g, --git treat FILE as a single commit or git revision range
single git commit with:
@@ -320,6 +322,7 @@ GetOptions(
'patch!' => \$chk_patch,
'emacs!' => \$emacs,
'terse!' => \$terse,
+ 'json!' => \$json,
'showfile!' => \$showfile,
'f|file!' => \$file,
'g|git!' => \$git,
@@ -379,6 +382,7 @@ help($help - 1) if ($help);
die "$P: --git cannot be used with --file or --fix\n" if ($git && ($file || $fix));
die "$P: --verbose cannot be used with --terse\n" if ($verbose && $terse);
+die "$P: --json cannot be used with --terse or --emacs\n" if ($json && ($terse || $emacs));
if ($color =~ /^[01]$/) {
$color = !$color;
@@ -1351,7 +1355,7 @@ for my $filename (@ARGV) {
}
close($FILE);
- if ($#ARGV > 0 && $quiet == 0) {
+ if (!$json && $#ARGV > 0 && $quiet == 0) {
print '-' x length($vname) . "\n";
print "$vname\n";
print '-' x length($vname) . "\n";
@@ -1372,7 +1376,7 @@ for my $filename (@ARGV) {
$file = $oldfile if ($is_git_file);
}
-if (!$quiet) {
+if (!$quiet && !$json) {
hash_show_words(\%use_type, "Used");
hash_show_words(\%ignore_type, "Ignored");
@@ -2395,6 +2399,18 @@ sub report {
push(our @report, $output);
+ if ($json) {
+ our ($realfile, $realline);
+ my %issue = (
+ level => $level,
+ type => $type,
+ message => $msg,
+ );
+ $issue{file} = $realfile if (defined $realfile && $realfile ne '');
+ $issue{line} = $realline + 0 if (defined $realline && $realline);
+ push(our @json_issues, \%issue);
+ }
+
return 1;
}
@@ -2402,6 +2418,34 @@ sub report_dump {
our @report;
}
+sub json_escape {
+ my ($str) = @_;
+ $str =~ s/\\/\\\\/g;
+ $str =~ s/"/\\"/g;
+ $str =~ s/\n/\\n/g;
+ $str =~ s/\r/\\r/g;
+ $str =~ s/\t/\\t/g;
+ $str =~ s/\x08/\\b/g;
+ $str =~ s/\x0c/\\f/g;
+ $str =~ s/([\x00-\x07\x0b\x0e-\x1f])/sprintf("\\u%04x", ord($1))/ge;
+ return $str;
+}
+
+sub json_encode_issue {
+ my ($issue) = @_;
+ my @fields;
+ push(@fields, '"level":"' . json_escape($issue->{level}) . '"');
+ push(@fields, '"type":"' . json_escape($issue->{type}) . '"');
+ push(@fields, '"message":"' . json_escape($issue->{message}) . '"');
+ if (defined $issue->{file}) {
+ push(@fields, '"file":"' . json_escape($issue->{file}) . '"');
+ }
+ if (defined $issue->{line}) {
+ push(@fields, '"line":' . ($issue->{line} + 0));
+ }
+ return '{' . join(',', @fields) . '}';
+}
+
sub fixup_current_range {
my ($lineRef, $offset, $length) = @_;
@@ -2690,14 +2734,15 @@ sub process {
my $last_coalesced_string_linenr = -1;
our @report = ();
+ our @json_issues = ();
our $cnt_lines = 0;
our $cnt_error = 0;
our $cnt_warn = 0;
our $cnt_chk = 0;
# Trace the real file/line as we go.
- my $realfile = '';
- my $realline = 0;
+ our $realfile = '';
+ our $realline = 0;
my $realcnt = 0;
my $here = '';
my $context_function; #undef'd unless there's a known function
@@ -7791,18 +7836,33 @@ sub process {
# If we have no input at all, then there is nothing to report on
# so just keep quiet.
if ($#rawlines == -1) {
+ if ($json) {
+ print '{"filename":"' . json_escape($filename) .
+ '","total_errors":0,"total_warnings":0,' .
+ '"total_checks":0,"total_lines":0,"issues":[]}' . "\n";
+ }
exit(0);
}
# In mailback mode only produce a report in the negative, for
# things that appear to be patches.
if ($mailback && ($clean == 1 || !$is_patch)) {
+ if ($json) {
+ print '{"filename":"' . json_escape($filename) .
+ '","total_errors":0,"total_warnings":0,' .
+ '"total_checks":0,"total_lines":0,"issues":[]}' . "\n";
+ }
exit(0);
}
# This is not a patch, and we are in 'no-patch' mode so
# just keep quiet.
if (!$chk_patch && !$is_patch) {
+ if ($json) {
+ print '{"filename":"' . json_escape($filename) .
+ '","total_errors":0,"total_warnings":0,' .
+ '"total_checks":0,"total_lines":0,"issues":[]}' . "\n";
+ }
exit(0);
}
@@ -7850,6 +7910,19 @@ sub process {
}
}
+ if ($json) {
+ my @issue_strings;
+ foreach my $issue (@json_issues) {
+ push(@issue_strings, json_encode_issue($issue));
+ }
+ print '{"filename":"' . json_escape($filename) . '",' .
+ '"total_errors":' . ($cnt_error + 0) . ',' .
+ '"total_warnings":' . ($cnt_warn + 0) . ',' .
+ '"total_checks":' . ($cnt_chk + 0) . ',' .
+ '"total_lines":' . ($cnt_lines + 0) . ',' .
+ '"issues":[' . join(',', @issue_strings) . ']' .
+ '}' . "\n";
+ } else {
print report_dump();
if ($summary && !($clean == 1 && $quiet == 1)) {
print "$filename " if ($summary_file);
@@ -7878,8 +7951,9 @@ NOTE: Whitespace errors detected.
EOM
}
}
+ } # end !$json
- if ($clean == 0 && $fix &&
+ if (!$json && $clean == 0 && $fix &&
("@rawlines" ne "@fixed" ||
$#fixed_inserted >= 0 || $#fixed_deleted >= 0)) {
my $newfile = $filename;
@@ -7918,7 +7992,7 @@ EOM
}
}
- if ($quiet == 0) {
+ if (!$json && $quiet == 0) {
print "\n";
if ($clean == 1) {
print "$vname has no obvious style problems and is ready for submission.\n";
--
2.53.0
next reply other threads:[~2026-04-06 17:00 UTC|newest]
Thread overview: 14+ messages / expand[flat|nested] mbox.gz Atom feed top
2026-04-06 17:00 Sasha Levin [this message]
2026-04-06 19:00 ` [PATCH] checkpatch: add --json output mode Konstantin Ryabitsev
2026-04-06 19:13 ` Sasha Levin
2026-04-06 19:22 ` Konstantin Ryabitsev
2026-04-06 20:34 ` Joe Perches
2026-04-06 20:41 ` Joe Perches
2026-04-08 17:24 ` [PATCH v2] " Sasha Levin
2026-04-08 18:16 ` Joe Perches
2026-04-25 20:04 ` [PATCH v3] " Sasha Levin
2026-04-25 21:52 ` Joe Perches
2026-04-26 0:07 ` Sasha Levin
2026-04-26 1:12 ` Joe Perches
2026-04-26 11:23 ` Sasha Levin
2026-04-26 9:47 ` Joe Perches
Reply instructions:
You may reply publicly to this message via plain-text email
using any one of the following methods:
* Save the following mbox file, import it into your mail client,
and reply-to-all from there: mbox
Avoid top-posting and favor interleaved quoting:
https://en.wikipedia.org/wiki/Posting_style#Interleaved_style
* Reply using the --to, --cc, and --in-reply-to
switches of git-send-email(1):
git send-email \
--in-reply-to=20260406170039.4034716-1-sashal@kernel.org \
--to=sashal@kernel.org \
--cc=apw@canonical.com \
--cc=corbet@lwn.net \
--cc=dwaipayanray1@gmail.com \
--cc=joe@perches.com \
--cc=linux-doc@vger.kernel.org \
--cc=linux-kernel@vger.kernel.org \
--cc=lukas.bulwahn@gmail.com \
--cc=skhan@linuxfoundation.org \
--cc=workflows@vger.kernel.org \
/path/to/YOUR_REPLY
https://kernel.org/pub/software/scm/git/docs/git-send-email.html
* If your mail client supports setting the In-Reply-To header
via mailto: links, try the mailto: link
Be sure your reply has a Subject: header at the top and a blank line
before the message body.
This is an external index of several public inboxes,
see mirroring instructions on how to clone and mirror
all data and code used by this external index.