From: Matthew Martin Subject: mandoc lint flag ordering To: schwarze@usta.de, tech@openbsd.org Date: Fri, 26 Dec 2025 23:32:27 -0600 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: