From 76a8994bd5d58138542e7bb0ce8dd0ceef778891 Mon Sep 17 00:00:00 2001 From: Mattias Andrée Date: Sat, 1 Jan 2022 23:54:20 +0100 Subject: Lint line continuation and whitespace issues MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit Signed-off-by: Mattias Andrée --- common.h | 46 +++++++++++++++++--- mklint.c | 147 ++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++++- text.c | 23 ++++++++++ ui.c | 13 ++++++ 4 files changed, 220 insertions(+), 9 deletions(-) diff --git a/common.h b/common.h index 1e42470..01a8c0d 100644 --- a/common.h +++ b/common.h @@ -5,13 +5,23 @@ #include #include + +#if defined(__GNUC__) +# pragma GCC diagnostic ignored "-Wsuggest-attribute=format" +# pragma GCC diagnostic ignored "-Wsuggest-attribute=noreturn" +# pragma GCC diagnostic ignored "-Wsuggest-attribute=pure" +# pragma GCC diagnostic ignored "-Wsuggest-attribute=const" +#endif + + #define EXIT_STYLE 1 -#define EXIT_WARNING 2 -#define EXIT_UNSPECIFIED 3 -#define EXIT_NONCONFIRMING 4 -#define EXIT_UNDEFINED 5 -#define EXIT_CRITICAL 6 -#define EXIT_ERROR 7 +#define EXIT_CONFUSING 2 +#define EXIT_WARNING 3 +#define EXIT_UNSPECIFIED 4 +#define EXIT_NONCONFORMING 5 +#define EXIT_UNDEFINED 6 +#define EXIT_CRITICAL 7 +#define EXIT_ERROR 8 #define LIST_WARNING_CLASSES(X)\ @@ -20,7 +30,15 @@ X(WC_CMDLINE, "cmdline", WARN)\ X(WC_TEXT, "text", WARN)\ X(WC_ENCODING, "encoding", WARN)\ - X(WC_LONG_LINE, "long-line", WARN_STYLE) + X(WC_LONG_LINE, "long-line", WARN_STYLE)\ + X(WC_NONEMPTY_BLANK, "nonempty-blank", WARN_STYLE)\ + X(WC_LEADING_BAD_SPACE, "leading-bad-space", WARN)\ + X(WC_ILLEGAL_INDENT, "illegal-indent", WARN)\ + X(WC_CONTINUATION_OF_BLANK, "continuation-of-blank", WARN)\ + X(WC_CONTINUATION_TO_BLANK, "continuation-to-blank", WARN)\ + X(WC_EOF_LINE_CONTINUATION, "eof-line-continuation", WARN)\ + X(WC_UNINDENTED_CONTINUATION, "unindented-continuation", WARN)\ + X(WC_SPACELESS_CONTINUATION, "spaceless-continuation", WARN) enum action { @@ -42,6 +60,14 @@ struct warning_class_data { enum action action; }; +enum line_class { + EMPTY, /* Classified as comment lines in the specification */ + BLANK, /* Classified as comment lines in the specification */ + COMMENT, + COMMAND_LINE, + OTHER +}; + struct line { char *data; size_t len; @@ -49,10 +75,12 @@ struct line { size_t lineno; int eof; int nest_level; + char continuation_joiner; }; struct style { size_t max_line_length; + int only_empty_blank_lines; }; @@ -70,6 +98,7 @@ struct line *load_makefile(const char *path, size_t *nlinesp); struct line *load_text_file(int fd, const char *fname, int nest_level, size_t *nlinesp); void check_utf8_encoding(struct line *line); void check_column_count(struct line *line); +int is_line_blank(struct line *line); /* ui.c */ @@ -77,7 +106,10 @@ extern struct warning_class_data warning_classes[]; void xprintwarningf(enum warning_class class, int severity, const char *fmt, ...); #define printinfof(CLASS, ...) xprintwarningf(CLASS, 0, __VA_ARGS__) #define warnf_style(CLASS, ...) xprintwarningf(CLASS, EXIT_STYLE, __VA_ARGS__) +#define warnf_confusing(CLASS, ...) xprintwarningf(CLASS, EXIT_CONFUSING, __VA_ARGS__) #define warnf_warning(CLASS, ...) xprintwarningf(CLASS, EXIT_WARNING, __VA_ARGS__) #define warnf_unspecified(CLASS, ...) xprintwarningf(CLASS, EXIT_UNSPECIFIED, __VA_ARGS__) +#define warnf_nonconforming(CLASS, ...) xprintwarningf(CLASS, EXIT_NONCONFORMING, __VA_ARGS__) #define warnf_undefined(CLASS, ...) xprintwarningf(CLASS, EXIT_UNDEFINED, __VA_ARGS__) void printerrorf(const char *fmt, ...); +void printtipf(enum warning_class class, const char *fmt, ...); diff --git a/mklint.c b/mklint.c index 7302245..c21db40 100644 --- a/mklint.c +++ b/mklint.c @@ -7,10 +7,127 @@ NUSAGE(EXIT_ERROR, "[-f makefile]"); int exit_status = 0; struct style style = { - .max_line_length = 120 + .max_line_length = 120, + .only_empty_blank_lines = 1 }; +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' ? '\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: 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 ,"); + } + 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; + + 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" + " and , 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) { + return BLANK; + + } else if (line->data[0] == '\t') { + return COMMAND_LINE; + + } else { + return OTHER; + } +} + + int main(int argc, char *argv[]) { @@ -21,7 +138,7 @@ main(int argc, char *argv[]) libsimple_default_failure_exit = EXIT_ERROR; - /* make(1) shall support mixing of options and operands (uptil --) */ + /* make(1) shall support mixing of options and operands (up to --) */ ARGBEGIN { case 'f': cmdline_opt_f(ARG(), &path); @@ -43,6 +160,32 @@ main(int argc, char *argv[]) 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: + case OTHER: + break; + + default: + abort(); + } + } + free(lines); return exit_status; } diff --git a/text.c b/text.c index 0cb69e6..440ed7d 100644 --- a/text.c +++ b/text.c @@ -2,6 +2,15 @@ #include "common.h" +static void +print_long_line_tip(enum warning_class class) +{ + printtipf(class, "you can put a at the end of the line to continue " + "it on the next line, except in or immediately proceeding an " + "include line"); +} + + struct line * load_text_file(int fd, const char *fname, int nest_level, size_t *nlinesp) { @@ -81,6 +90,8 @@ load_text_file(int fd, const char *fname, int nest_level, size_t *nlinesp) "text files and POSIX only guarantees support for lines up to 2048 " "bytes long including the character in text files", fname, *nlinesp + 1); + printinfof(WC_TEXT, "this implementation supports arbitrarily long lines"); + print_long_line_tip(WC_TEXT); } p += lines[i].len + 1; } @@ -140,5 +151,17 @@ check_column_count(struct line *line) if (columns > style.max_line_length) { warnf_style(WC_LONG_LINE, "%s:%zu: line is longer than %zu columns", line->path, line->lineno, columns); + if (line->len + 1 <= 2048) + print_long_line_tip(WC_LONG_LINE); } } + + +int +is_line_blank(struct line *line) +{ + char *s = line->data; + while (isspace(*s)) + s++; + return !!*s; +} diff --git a/ui.c b/ui.c index 610729b..1afff76 100644 --- a/ui.c +++ b/ui.c @@ -46,3 +46,16 @@ printerrorf(const char *fmt, ...) va_end(ap); exit(EXIT_CRITICAL); } + + +void +printtipf(enum warning_class class, const char *fmt, ...) +{ + va_list ap; + if (warning_classes[class].action != IGNORE) { + va_start(ap, fmt); + fprintf(stderr, "%s: [tip] ", argv0); + vfprintf(stderr, fmt, ap); + va_end(ap); + } +} -- cgit v1.2.3-70-g09d2