summaryrefslogblamecommitdiffstats
path: root/makel.c
blob: e68f68a14c2e81e09810b19bb5cd7e863f423d53 (plain) (tree)
1
2
3
4
5
6
7
8
9
10


                                                         






                                                     

 

                    
                      
                               

                                    


  





                                                                           
                                                                               
































                                                                                                                         

                                                                                                



























                                                                                                                        








                                 
           






                                                                                              
                                                                                              
















                                                                                                                 



                                                





                                           







                                                                                                




                             



                                

                           
                 
 
                                                                             











                                            
                                                           
 





                                               
 

















                                                                                                     







                                                                                                        
                           


                                                                                                     




                                

                                                      
                                                                                                                              






                                                                                                 








                                                                                  

         


                           
/* See LICENSE file for copyright and license details. */
#include "common.h"

const char *argv0 = "makel";

static void
usage(void) {
	fprintf(stderr, "%s [-f makefile]\n", argv0);
	exit(EXIT_ERROR);
}


int exit_status = 0;

struct style style = {
	.max_line_length = 120,
	.only_empty_blank_lines = 1,
	.macro_bracket_style = ROUND
};


static void
set_line_continuation_joiner(struct line *line)
{
	if (line->len && line->data[line->len - 1] == '\\') {
		line->data[--line->len] = '\0';
		/* Doesn't matter here if the first non-white space is # */
		line->continuation_joiner = line->data[0] == '\t' ? '\\' : ' ';
	} else {
		line->continuation_joiner = '\0';
	}
}


static void
check_line_continuations(struct line *lines, size_t nlines)
{
	size_t i, cont_from = 0;

	for (i = 0; i < nlines; i++) {
		set_line_continuation_joiner(&lines[i]);

		if (lines[i].continuation_joiner &&
		    (!i || !lines[i - 1].continuation_joiner) &&
		    is_line_blank(&lines[i])) {
			warnf_confusing(WC_CONTINUATION_OF_BLANK,
			                "%s:%zu: initial line continuation on otherwise blank line, can cause confusion",
			                lines[i].path, lines[i].lineno);
		}

		if (!lines[i].continuation_joiner &&
		    i && lines[i - 1].continuation_joiner) {
			warnf_confusing(WC_CONTINUATION_TO_BLANK,
			                "%s:%zu: terminal line continuation to blank line, can cause confusion",
			                lines[i].path, lines[i].lineno);
		}

		if (!lines[i].continuation_joiner && lines[i].eof) {
			warnf_unspecified(WC_EOF_LINE_CONTINUATION,
			                  "%s:%zu: line continuation at end of file, causes unspecified behaviour%s",
			                  lines[i].path, lines[i].lineno,
			                  !lines[i].nest_level ? "" :
			                  ", it is especially problematic in an included line");
			printinfof(WC_EOF_LINE_CONTINUATION, "this implementation will remove the line continuation");
			lines[i].continuation_joiner = 0;
		}

		if (i && lines[i - 1].continuation_joiner && lines[i].len) {
			if (!isspace(lines[i].data[0])) {
				if (lines[cont_from].len && !isspace(lines[cont_from].data[lines[cont_from].len - 1])) {
					warnf_confusing(WC_SPACELESS_CONTINUATION,
					                "%s:%zu,%zu: <backslash> is proceeded by a non-white space "
					                "character at the same time as the next line%s begins with "
					                "a non-white space character, this can cause confusion as "
					                "the make utility will add a whitespace",
					                lines[cont_from].path, lines[cont_from].lineno,
					                lines[i].lineno, i == cont_from + 1 ? "" :
					                ", that consist of not only a <backslash>,");
				}
				warnf_confusing(WC_UNINDENTED_CONTINUATION,
				                "%s:%zu: continuation of line is not indented, can cause confusion",
				                lines[i].path, lines[i].lineno);
			}
			cont_from = i;
		} else if (lines[i].continuation_joiner) {
			cont_from = i;
		}
	}
}


static enum line_class
classify_line(struct line *line)
{
	int warned_bad_space = 0;
	char *s;

	if (!line->len)
		return EMPTY;

start_over:
	s = line->data;

	while (isspace(*s)) {
		if (!warned_bad_space && !isblank(*s)) {
			warned_bad_space = 1;
			warnf_undefined(WC_LEADING_BAD_SPACE,
			                "%s:%zu: line contains leading white space other than"
		        	        "<space> and <tab>, which causes undefined behaviour",
			                line->path, line->lineno);
			/* TODO what do we do here? */
		}
		s++;
	}

	if (*s == '#') {
		if (line->data[0] != '#') {
			/* TODO should not apply if command line */
			warnf_undefined(WC_ILLEGAL_INDENT,
			                "%s:%zu: comment has leading white space, which is not legal",
			                line->path, line->lineno);
			printinfof(WC_ILLEGAL_INDENT, "this implementation will recognise it as a comment line");
		}
		return COMMENT;

	} else if (!*s) {
		if (line->continuation_joiner) {
			line++;
			goto start_over;
		}
		return BLANK;

	} else if (line->data[0] == '\t') {
		return COMMAND_LINE;

	} else {
		if (*s == '-') { /* We will warn about this later */
			s++;
			while (isspace(*s))
				s++;
		}

		/* TODO unspecified behaviour if include line with <backslash> */
		/* TODO unspecified behaviour if continuation that looks like an include line */
		return OTHER;
	}
}


int
main(int argc, char *argv[])
{
	const char *path = NULL;
	struct line *lines;
	size_t nlines;
	size_t i;

	/* make(1) shall support mixing of options and operands (up to --) */
	ARGBEGIN {
	case 'f':
		cmdline_opt_f(ARG(), &path);
		break;

	default:
		usage();
	} ARGEND;

	if (argc)
		usage();

	setlocale(LC_ALL, ""); /* Required by wcwidth(3) */

	lines = load_makefile(path, &nlines);

	for (i = 0; i < nlines; i++) {
		check_utf8_encoding(&lines[i]);
		check_column_count(&lines[i]);
	}

	check_line_continuations(lines, nlines);

	for (i = 0; i < nlines; i++) {
		switch (classify_line(&lines[i])) {
		case EMPTY:
			break;

		case BLANK:
			if (style.only_empty_blank_lines) {
				warnf_style(WC_NONEMPTY_BLANK, "%s:%zu: line is blank but not empty",
				            lines[i].path, lines[i].lineno);
			}
			break;

		case COMMENT:
			break;

		case COMMAND_LINE:
			/* TODO list may, for historical reasons, end at a comment line;
			 *      note, the specifications specify “comment line” which is
			 *      define to include empty and blank lines; note however
			 *      that a line that begins with a <hash> that is prefixed
			 *      by whitespace is not a comment line, so, if it begins
			 *      with <tab> followed by zero or more whitespace, and then
			 *      a <hash>, it a command line, not a comment line. */
			/* TODO on line continuation, remove first '\t', if any, and join with '\\\n' */
		case OTHER:
			/* TODO first non-comment line shall be special target .POSIX without
			 *      prerequisites or commands, behaviour is unspecified otherwise */
			/* TODO on line continuation, remove leading white space and join with ' ' */
			break;

		default:
			abort();
		}

		while (lines[i].continuation_joiner) {
			if (memchr(lines[i].data, '#', lines[i].len)) { /* TODO could also be a non-standard internal macro */
				warnf_confusing(WC_COMMENT_CONTINUATION,
				                "%s:%zu: using continuation of line to continue "
				                "a comment on the next line can cause confusion",
				                lines[i].path, lines[i].lineno);
			}
			i += 1;
		}
		/* TODO # in comment lines are very problematic. In make(1)
		 *      a comment can have a line continuation, but in sh(1)
		 *      comments cannot have line continuation. Furthermore,
		 *      any #, even if there is a be backslashed or in quotes,
		 *      becomes a comment; because of this, some implementations
		 *      of make do not recognise comments in command lines and
		 *      instead rely on sh(1) ignoring comments (this however
		 *      breaks POSIX compliance). */
		/* TODO check if a # appears inside quotes or after a backslash */
	}

	free(lines);
	return exit_status;
}