Index | Thread | Search

From:
Matthew Martin <phy1729@gmail.com>
Subject:
mandoc lint flag ordering
To:
schwarze@usta.de, tech@openbsd.org
Date:
Fri, 26 Dec 2025 23:32:27 -0600

Download raw body.

Thread
  • Matthew Martin:

    mandoc lint flag ordering

With the patch to sort rsync.1 flags, I thought it might be nice if
mandoc -T lint warned on out of order flags. Is this a reasonable
lint to add?

If so, the below patch is a first stab that is not ready for commit.
Aside from style being off, there are a number of open questions.

How should state be kept between items? Is a static acceptable?

How should the last seen flag be reset between lists? The El macro
doesn't seem to be visited by mdoc_validate.

strcasecmp then strcmp works for alphabetic flags, but is probably wrong
for some symbols and multi-digit flags. Is there a better sort order?

Some man pages (e.g. grep, seq) list long only options after short
rather than interleaving long and short options. Should the lint ignore
those cases?

I doubt the child/next chasing to find the flag text is right.
The simple nit->head->child->child->string fails for cases like
apply.1: .It Fl Ns Ar #
pkill.1: .It Fl Ar signal

I don't know what to do for cases like indent.1: .It Fl \&bc , nbc


diff --git mandoc.h mandoc.h
index 1349fc033de..5ba234cd21e 100644
--- mandoc.h
+++ mandoc.h
@@ -69,6 +69,7 @@ enum	mandocerr {
 	MANDOCERR_BX, /* consider using OS macro: macro */
 	MANDOCERR_ER_ORDER, /* errnos out of order: Er ... */
 	MANDOCERR_ER_REP, /* duplicate errno: Er ... */
+	MANDOCERR_FL_ORDER, /* flags out of order: ... after ... */
 	MANDOCERR_XR_BAD,  /* referenced manual not found: Xr name sec */
 	MANDOCERR_DELIM, /* trailing delimiter: macro ... */
 	MANDOCERR_DELIM_NB, /* no blank before trailing delimiter: macro ... */
diff --git mandoc_msg.c mandoc_msg.c
index 08dc63cf7ad..72101436459 100644
--- mandoc_msg.c
+++ mandoc_msg.c
@@ -66,6 +66,7 @@ static	const char *const type_message[MANDOCERR_MAX] = {
 	"consider using OS macro",
 	"errnos out of order",
 	"duplicate errno",
+	"flags out of order",
 	"referenced manual not found",
 	"trailing delimiter",
 	"no blank before trailing delimiter",
diff --git mdoc_validate.c mdoc_validate.c
index 2a7b29ecc14..d07baf1218b 100644
--- mdoc_validate.c
+++ mdoc_validate.c
@@ -293,6 +293,7 @@ static	const char * const secnames[SEC__MAX] = {
 };
 
 static	int	  fn_prio = TAG_STRONG;
+static	char	 *last_flag = NULL;
 
 
 /* Validate the subtree rooted at mdoc->last. */
@@ -310,6 +311,10 @@ mdoc_validate(struct roff_man *mdoc)
 
 	n = mdoc->last;
 	switch (n->tok) {
+	case MDOC_Bl:
+		free(last_flag);
+		last_flag = NULL;
+		break;
 	case MDOC_Lp:
 		n->tok = MDOC_Pp;
 		break;
@@ -1714,8 +1719,9 @@ post_xx(POST_ARGS)
 static void
 post_it(POST_ARGS)
 {
-	struct roff_node *nbl, *nit, *nch;
-	int		  i, cols;
+	struct roff_node *nbl, *nit, *nch, *np;
+	const char	 *flag_str;
+	int		  i, cmp, cols;
 	enum mdoc_list	  lt;
 
 	post_prevpar(mdoc);
@@ -1737,6 +1743,32 @@ post_it(POST_ARGS)
 			mandoc_msg(MANDOCERR_IT_NOHEAD,
 			    nit->line, nit->pos, "Bl -%s It",
 			    mdoc_argnames[nbl->args->argv[0].arg]);
+		else if (nit->head->child->tok == MDOC_Fl) {
+			np = nit->head->child;
+			while (np->string == NULL) {
+				if (np->child != NULL)
+					np = np->child;
+				else if (np->next != NULL)
+					np = np->next;
+				else
+					break;
+
+			}
+			flag_str = np->string;
+			if (flag_str == NULL)
+				flag_str = "";
+			if (flag_str[0] == '-')
+				flag_str++;
+			if (last_flag != NULL) {
+				cmp = strcasecmp(last_flag, flag_str);
+				if (cmp > 0 ||
+					(cmp == 0 && strcmp(last_flag, flag_str) > 0))
+						mandoc_msg(MANDOCERR_FL_ORDER, np->line,
+						    np->pos, "%s after %s", flag_str, last_flag);
+				free(last_flag);
+			}
+			last_flag = mandoc_strdup(flag_str);
+		}
 		break;
 	case LIST_bullet:
 	case LIST_dash: