diff mbox series

[v2,6/7] doc lint: lint relative section order

Message ID patch-6.7-8c294afe2a-20210409T145728Z-avarab@gmail.com (mailing list archive)
State Accepted
Commit ea8b9271b17e64502dc80981cf09cec42739dce7
Headers show
Series doc make and lint fixes | expand

Commit Message

Ævar Arnfjörð Bjarmason April 9, 2021, 3:02 p.m. UTC
Add a linting script to check the relative order of the sections in
the documentation. We should have NAME, then SYNOPSIS, DESCRIPTION,
OPTIONS etc. in that order.

That holds true throughout our documentation, except for a few
exceptions which are hardcoded in the linting script.

Signed-off-by: Ævar Arnfjörð Bjarmason <avarab@gmail.com>
---
 Documentation/Makefile                    |   3 +-
 Documentation/lint-man-section-order.perl | 125 ++++++++++++++++++++++
 2 files changed, 127 insertions(+), 1 deletion(-)
 create mode 100755 Documentation/lint-man-section-order.perl
diff mbox series

Patch

diff --git a/Documentation/Makefile b/Documentation/Makefile
index 34b4f5716a..5e0828869b 100644
--- a/Documentation/Makefile
+++ b/Documentation/Makefile
@@ -483,7 +483,8 @@  lint-docs::
 		--section=1 $(MAN1_TXT) \
 		--section=5 $(MAN5_TXT) \
 		--section=7 $(MAN7_TXT); \
-	$(PERL_PATH) lint-man-end-blurb.perl $(MAN_TXT)
+	$(PERL_PATH) lint-man-end-blurb.perl $(MAN_TXT); \
+	$(PERL_PATH) lint-man-section-order.perl $(MAN_TXT);
 
 ifeq ($(wildcard po/Makefile),po/Makefile)
 doc-l10n install-l10n::
diff --git a/Documentation/lint-man-section-order.perl b/Documentation/lint-man-section-order.perl
new file mode 100755
index 0000000000..5767e7e456
--- /dev/null
+++ b/Documentation/lint-man-section-order.perl
@@ -0,0 +1,125 @@ 
+#!/usr/bin/perl
+
+use strict;
+use warnings;
+
+my %SECTIONS;
+{
+	my $order = 0;
+	%SECTIONS = (
+		'NAME' => {
+			required => 1,
+			order => $order++,
+		},
+		'SYNOPSIS' => {
+			required => 1,
+			order => $order++,
+		},
+		'DESCRIPTION' => {
+			required => 1,
+			order => $order++,
+			bad => {
+				'git-mktag.txt' => 'OPTIONS',
+				'git-cvsserver.txt' => 'OPTIONS',
+			},
+		},
+		'OPTIONS' => {
+			order => $order++,
+			required => 0,
+			bad => {
+				'git-grep.txt' => 'CONFIGURATION',
+				'git-rebase.txt' => 'CONFIGURATION',
+			},
+		},
+		'CONFIGURATION' => {
+			order => $order++,
+			bad => {
+				'git-svn.txt' => 'BUGS',
+			},
+		},
+		'BUGS' => {
+			order => $order++,
+		},
+		'SEE ALSO' => {
+			order => $order++,
+		},
+		'GIT' => {
+			required => 1,
+			order => $order++,
+		},
+	);
+}
+my $SECTION_RX = do {
+	my ($names) = join "|", keys %SECTIONS;
+	qr/^($names)$/s;
+};
+
+my $exit_code = 0;
+sub report {
+	my ($msg) = @_;
+	print "$ARGV:$.: $msg\n";
+	$exit_code = 1;
+}
+
+my $last_was_section;
+my @actual_order;
+while (my $line = <>) {
+	chomp $line;
+	if ($line =~ $SECTION_RX) {
+		push @actual_order => $line;
+		$last_was_section = 1;
+		# Have no "last" section yet, processing NAME
+		next if @actual_order == 1;
+
+		my @expected_order = sort {
+			$SECTIONS{$a}->{order} <=> $SECTIONS{$b}->{order}
+		} @actual_order;
+
+		my $expected_last = $expected_order[-2];
+		my $actual_last = $actual_order[-2];
+		my $except_last = $SECTIONS{$line}->{bad}->{$ARGV} || '';
+		if (($SECTIONS{$line}->{bad}->{$ARGV} || '') eq $actual_last) {
+			# Either we're whitelisted, or ...
+			next
+		} elsif (exists $SECTIONS{$actual_last}->{bad}->{$ARGV}) {
+			# ... we're complaing about the next section
+			# which is out of order because this one is,
+			# don't complain about that one.
+			next;
+		} elsif ($actual_last ne $expected_last) {
+			report("section '$line' incorrectly ordered, comes after '$actual_last'");
+		}
+		next;
+	}
+	if ($last_was_section) {
+		my $last_section = $actual_order[-1];
+		if (length $last_section ne length $line) {
+			report("dashes under '$last_section' should match its length!");
+		}
+		if ($line !~ /^-+$/) {
+			report("dashes under '$last_section' should be '-' dashes!");
+		}
+		$last_was_section = 0;
+	}
+
+	if (eof) {
+		# We have both a hash and an array to consider, for
+		# convenience
+		my %actual_sections;
+		@actual_sections{@actual_order} = ();
+
+		for my $section (sort keys %SECTIONS) {
+			next if !$SECTIONS{$section}->{required} or exists $actual_sections{$section};
+			report("has no required '$section' section!");
+		}
+
+		# Reset per-file state
+		{
+			@actual_order = ();
+			# this resets our $. for each file
+			close ARGV;
+		}
+	}
+}
+
+exit $exit_code;