]> git.cameronkatri.com Git - mandoc.git/blob - man_validate.c
Use "-" rather than "\(hy" for the heads of .Bl -dash and -hyphen lists.
[mandoc.git] / man_validate.c
1 /* $OpenBSD$ */
2 /*
3 * Copyright (c) 2008, 2009, 2010, 2011 Kristaps Dzonsons <kristaps@bsd.lv>
4 * Copyright (c) 2010, 2012-2015 Ingo Schwarze <schwarze@openbsd.org>
5 *
6 * Permission to use, copy, modify, and distribute this software for any
7 * purpose with or without fee is hereby granted, provided that the above
8 * copyright notice and this permission notice appear in all copies.
9 *
10 * THE SOFTWARE IS PROVIDED "AS IS" AND THE AUTHORS DISCLAIM ALL WARRANTIES
11 * WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF
12 * MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHORS BE LIABLE FOR
13 * ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES
14 * WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN
15 * ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF
16 * OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE.
17 */
18 #include "config.h"
19
20 #include <sys/types.h>
21
22 #include <assert.h>
23 #include <ctype.h>
24 #include <errno.h>
25 #include <limits.h>
26 #include <stdarg.h>
27 #include <stdlib.h>
28 #include <string.h>
29 #include <time.h>
30
31 #include "mandoc_aux.h"
32 #include "mandoc.h"
33 #include "roff.h"
34 #include "man.h"
35 #include "libmandoc.h"
36 #include "roff_int.h"
37 #include "libman.h"
38
39 #define CHKARGS struct roff_man *man, struct roff_node *n
40
41 typedef void (*v_check)(CHKARGS);
42
43 static void check_par(CHKARGS);
44 static void check_part(CHKARGS);
45 static void check_root(CHKARGS);
46 static void check_text(CHKARGS);
47
48 static void post_AT(CHKARGS);
49 static void post_IP(CHKARGS);
50 static void post_vs(CHKARGS);
51 static void post_fi(CHKARGS);
52 static void post_ft(CHKARGS);
53 static void post_nf(CHKARGS);
54 static void post_OP(CHKARGS);
55 static void post_TH(CHKARGS);
56 static void post_UC(CHKARGS);
57 static void post_UR(CHKARGS);
58
59 static v_check man_valids[MAN_MAX] = {
60 post_vs, /* br */
61 post_TH, /* TH */
62 NULL, /* SH */
63 NULL, /* SS */
64 NULL, /* TP */
65 check_par, /* LP */
66 check_par, /* PP */
67 check_par, /* P */
68 post_IP, /* IP */
69 NULL, /* HP */
70 NULL, /* SM */
71 NULL, /* SB */
72 NULL, /* BI */
73 NULL, /* IB */
74 NULL, /* BR */
75 NULL, /* RB */
76 NULL, /* R */
77 NULL, /* B */
78 NULL, /* I */
79 NULL, /* IR */
80 NULL, /* RI */
81 post_vs, /* sp */
82 post_nf, /* nf */
83 post_fi, /* fi */
84 NULL, /* RE */
85 check_part, /* RS */
86 NULL, /* DT */
87 post_UC, /* UC */
88 NULL, /* PD */
89 post_AT, /* AT */
90 NULL, /* in */
91 post_ft, /* ft */
92 post_OP, /* OP */
93 post_nf, /* EX */
94 post_fi, /* EE */
95 post_UR, /* UR */
96 NULL, /* UE */
97 NULL, /* ll */
98 };
99
100
101 void
102 man_valid_post(struct roff_man *man)
103 {
104 struct roff_node *n;
105 v_check *cp;
106
107 n = man->last;
108 if (n->flags & MAN_VALID)
109 return;
110 n->flags |= MAN_VALID;
111
112 switch (n->type) {
113 case ROFFT_TEXT:
114 check_text(man, n);
115 break;
116 case ROFFT_ROOT:
117 check_root(man, n);
118 break;
119 case ROFFT_EQN:
120 case ROFFT_TBL:
121 break;
122 default:
123 cp = man_valids + n->tok;
124 if (*cp)
125 (*cp)(man, n);
126 break;
127 }
128 }
129
130 static void
131 check_root(CHKARGS)
132 {
133
134 assert((man->flags & (MAN_BLINE | MAN_ELINE)) == 0);
135
136 if (NULL == man->first->child)
137 mandoc_msg(MANDOCERR_DOC_EMPTY, man->parse,
138 n->line, n->pos, NULL);
139 else
140 man->meta.hasbody = 1;
141
142 if (NULL == man->meta.title) {
143 mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
144 n->line, n->pos, NULL);
145
146 /*
147 * If a title hasn't been set, do so now (by
148 * implication, date and section also aren't set).
149 */
150
151 man->meta.title = mandoc_strdup("");
152 man->meta.msec = mandoc_strdup("");
153 man->meta.date = man->quick ? mandoc_strdup("") :
154 mandoc_normdate(man->parse, NULL, n->line, n->pos);
155 }
156 }
157
158 static void
159 check_text(CHKARGS)
160 {
161 char *cp, *p;
162
163 if (MAN_LITERAL & man->flags)
164 return;
165
166 cp = n->string;
167 for (p = cp; NULL != (p = strchr(p, '\t')); p++)
168 mandoc_msg(MANDOCERR_FI_TAB, man->parse,
169 n->line, n->pos + (p - cp), NULL);
170 }
171
172 static void
173 post_OP(CHKARGS)
174 {
175
176 if (n->nchild == 0)
177 mandoc_msg(MANDOCERR_OP_EMPTY, man->parse,
178 n->line, n->pos, "OP");
179 else if (n->nchild > 2) {
180 n = n->child->next->next;
181 mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
182 n->line, n->pos, "OP ... %s", n->string);
183 }
184 }
185
186 static void
187 post_UR(CHKARGS)
188 {
189
190 if (n->type == ROFFT_HEAD && n->child == NULL)
191 mandoc_vmsg(MANDOCERR_UR_NOHEAD, man->parse,
192 n->line, n->pos, "UR");
193 check_part(man, n);
194 }
195
196 static void
197 post_ft(CHKARGS)
198 {
199 char *cp;
200 int ok;
201
202 if (0 == n->nchild)
203 return;
204
205 ok = 0;
206 cp = n->child->string;
207 switch (*cp) {
208 case '1':
209 case '2':
210 case '3':
211 case '4':
212 case 'I':
213 case 'P':
214 case 'R':
215 if ('\0' == cp[1])
216 ok = 1;
217 break;
218 case 'B':
219 if ('\0' == cp[1] || ('I' == cp[1] && '\0' == cp[2]))
220 ok = 1;
221 break;
222 case 'C':
223 if ('W' == cp[1] && '\0' == cp[2])
224 ok = 1;
225 break;
226 default:
227 break;
228 }
229
230 if (0 == ok) {
231 mandoc_vmsg(MANDOCERR_FT_BAD, man->parse,
232 n->line, n->pos, "ft %s", cp);
233 *cp = '\0';
234 }
235 }
236
237 static void
238 check_part(CHKARGS)
239 {
240
241 if (n->type == ROFFT_BODY && n->child == NULL)
242 mandoc_msg(MANDOCERR_BLK_EMPTY, man->parse,
243 n->line, n->pos, man_macronames[n->tok]);
244 }
245
246 static void
247 check_par(CHKARGS)
248 {
249
250 switch (n->type) {
251 case ROFFT_BLOCK:
252 if (0 == n->body->nchild)
253 roff_node_delete(man, n);
254 break;
255 case ROFFT_BODY:
256 if (0 == n->nchild)
257 mandoc_vmsg(MANDOCERR_PAR_SKIP,
258 man->parse, n->line, n->pos,
259 "%s empty", man_macronames[n->tok]);
260 break;
261 case ROFFT_HEAD:
262 if (n->nchild)
263 mandoc_vmsg(MANDOCERR_ARG_SKIP,
264 man->parse, n->line, n->pos,
265 "%s %s%s", man_macronames[n->tok],
266 n->child->string,
267 n->nchild > 1 ? " ..." : "");
268 break;
269 default:
270 break;
271 }
272 }
273
274 static void
275 post_IP(CHKARGS)
276 {
277
278 switch (n->type) {
279 case ROFFT_BLOCK:
280 if (0 == n->head->nchild && 0 == n->body->nchild)
281 roff_node_delete(man, n);
282 break;
283 case ROFFT_BODY:
284 if (0 == n->parent->head->nchild && 0 == n->nchild)
285 mandoc_vmsg(MANDOCERR_PAR_SKIP,
286 man->parse, n->line, n->pos,
287 "%s empty", man_macronames[n->tok]);
288 break;
289 default:
290 break;
291 }
292 }
293
294 static void
295 post_TH(CHKARGS)
296 {
297 struct roff_node *nb;
298 const char *p;
299
300 free(man->meta.title);
301 free(man->meta.vol);
302 free(man->meta.os);
303 free(man->meta.msec);
304 free(man->meta.date);
305
306 man->meta.title = man->meta.vol = man->meta.date =
307 man->meta.msec = man->meta.os = NULL;
308
309 nb = n;
310
311 /* ->TITLE<- MSEC DATE OS VOL */
312
313 n = n->child;
314 if (n && n->string) {
315 for (p = n->string; '\0' != *p; p++) {
316 /* Only warn about this once... */
317 if (isalpha((unsigned char)*p) &&
318 ! isupper((unsigned char)*p)) {
319 mandoc_vmsg(MANDOCERR_TITLE_CASE,
320 man->parse, n->line,
321 n->pos + (p - n->string),
322 "TH %s", n->string);
323 break;
324 }
325 }
326 man->meta.title = mandoc_strdup(n->string);
327 } else {
328 man->meta.title = mandoc_strdup("");
329 mandoc_msg(MANDOCERR_TH_NOTITLE, man->parse,
330 nb->line, nb->pos, "TH");
331 }
332
333 /* TITLE ->MSEC<- DATE OS VOL */
334
335 if (n)
336 n = n->next;
337 if (n && n->string)
338 man->meta.msec = mandoc_strdup(n->string);
339 else {
340 man->meta.msec = mandoc_strdup("");
341 mandoc_vmsg(MANDOCERR_MSEC_MISSING, man->parse,
342 nb->line, nb->pos, "TH %s", man->meta.title);
343 }
344
345 /* TITLE MSEC ->DATE<- OS VOL */
346
347 if (n)
348 n = n->next;
349 if (n && n->string && '\0' != n->string[0]) {
350 man->meta.date = man->quick ?
351 mandoc_strdup(n->string) :
352 mandoc_normdate(man->parse, n->string,
353 n->line, n->pos);
354 } else {
355 man->meta.date = mandoc_strdup("");
356 mandoc_msg(MANDOCERR_DATE_MISSING, man->parse,
357 n ? n->line : nb->line,
358 n ? n->pos : nb->pos, "TH");
359 }
360
361 /* TITLE MSEC DATE ->OS<- VOL */
362
363 if (n && (n = n->next))
364 man->meta.os = mandoc_strdup(n->string);
365 else if (man->defos != NULL)
366 man->meta.os = mandoc_strdup(man->defos);
367
368 /* TITLE MSEC DATE OS ->VOL<- */
369 /* If missing, use the default VOL name for MSEC. */
370
371 if (n && (n = n->next))
372 man->meta.vol = mandoc_strdup(n->string);
373 else if ('\0' != man->meta.msec[0] &&
374 (NULL != (p = mandoc_a2msec(man->meta.msec))))
375 man->meta.vol = mandoc_strdup(p);
376
377 if (n != NULL && (n = n->next) != NULL)
378 mandoc_vmsg(MANDOCERR_ARG_EXCESS, man->parse,
379 n->line, n->pos, "TH ... %s", n->string);
380
381 /*
382 * Remove the `TH' node after we've processed it for our
383 * meta-data.
384 */
385 roff_node_delete(man, man->last);
386 }
387
388 static void
389 post_nf(CHKARGS)
390 {
391
392 if (man->flags & MAN_LITERAL)
393 mandoc_msg(MANDOCERR_NF_SKIP, man->parse,
394 n->line, n->pos, "nf");
395
396 man->flags |= MAN_LITERAL;
397 }
398
399 static void
400 post_fi(CHKARGS)
401 {
402
403 if ( ! (MAN_LITERAL & man->flags))
404 mandoc_msg(MANDOCERR_FI_SKIP, man->parse,
405 n->line, n->pos, "fi");
406
407 man->flags &= ~MAN_LITERAL;
408 }
409
410 static void
411 post_UC(CHKARGS)
412 {
413 static const char * const bsd_versions[] = {
414 "3rd Berkeley Distribution",
415 "4th Berkeley Distribution",
416 "4.2 Berkeley Distribution",
417 "4.3 Berkeley Distribution",
418 "4.4 Berkeley Distribution",
419 };
420
421 const char *p, *s;
422
423 n = n->child;
424
425 if (n == NULL || n->type != ROFFT_TEXT)
426 p = bsd_versions[0];
427 else {
428 s = n->string;
429 if (0 == strcmp(s, "3"))
430 p = bsd_versions[0];
431 else if (0 == strcmp(s, "4"))
432 p = bsd_versions[1];
433 else if (0 == strcmp(s, "5"))
434 p = bsd_versions[2];
435 else if (0 == strcmp(s, "6"))
436 p = bsd_versions[3];
437 else if (0 == strcmp(s, "7"))
438 p = bsd_versions[4];
439 else
440 p = bsd_versions[0];
441 }
442
443 free(man->meta.os);
444 man->meta.os = mandoc_strdup(p);
445 }
446
447 static void
448 post_AT(CHKARGS)
449 {
450 static const char * const unix_versions[] = {
451 "7th Edition",
452 "System III",
453 "System V",
454 "System V Release 2",
455 };
456
457 struct roff_node *nn;
458 const char *p, *s;
459
460 n = n->child;
461
462 if (n == NULL || n->type != ROFFT_TEXT)
463 p = unix_versions[0];
464 else {
465 s = n->string;
466 if (0 == strcmp(s, "3"))
467 p = unix_versions[0];
468 else if (0 == strcmp(s, "4"))
469 p = unix_versions[1];
470 else if (0 == strcmp(s, "5")) {
471 nn = n->next;
472 if (nn != NULL &&
473 nn->type == ROFFT_TEXT &&
474 nn->string[0] != '\0')
475 p = unix_versions[3];
476 else
477 p = unix_versions[2];
478 } else
479 p = unix_versions[0];
480 }
481
482 free(man->meta.os);
483 man->meta.os = mandoc_strdup(p);
484 }
485
486 static void
487 post_vs(CHKARGS)
488 {
489
490 if (NULL != n->prev)
491 return;
492
493 switch (n->parent->tok) {
494 case MAN_SH:
495 case MAN_SS:
496 mandoc_vmsg(MANDOCERR_PAR_SKIP, man->parse, n->line, n->pos,
497 "%s after %s", man_macronames[n->tok],
498 man_macronames[n->parent->tok]);
499 /* FALLTHROUGH */
500 case TOKEN_NONE:
501 /*
502 * Don't warn about this because it occurs in pod2man
503 * and would cause considerable (unfixable) warnage.
504 */
505 roff_node_delete(man, n);
506 break;
507 default:
508 break;
509 }
510 }