summaryrefslogtreecommitdiffstats
diff options
context:
space:
mode:
authorMattias Andrée <maandree@kth.se>2022-01-01 23:54:20 +0100
committerMattias Andrée <maandree@kth.se>2022-01-01 23:54:20 +0100
commit76a8994bd5d58138542e7bb0ce8dd0ceef778891 (patch)
tree99a6cd09ac8c1ec5dffb64bb510d205560f0cdc3
parentMove some functions into makefile.c (diff)
downloadmakel-76a8994bd5d58138542e7bb0ce8dd0ceef778891.tar.gz
makel-76a8994bd5d58138542e7bb0ce8dd0ceef778891.tar.bz2
makel-76a8994bd5d58138542e7bb0ce8dd0ceef778891.tar.xz
Lint line continuation and whitespace issues
Signed-off-by: Mattias Andrée <maandree@kth.se>
-rw-r--r--common.h46
-rw-r--r--mklint.c147
-rw-r--r--text.c23
-rw-r--r--ui.c13
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 <libsimple-arg.h>
#include <grapheme.h>
+
+#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: <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;
+
+ 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) {
+ 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 <backslash> 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 <newline> 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);
+ }
+}