Git fork
at reftables-rust 391 lines 9.7 kB view raw
1/* 2 * Whitespace rules 3 * 4 * Copyright (c) 2007 Junio C Hamano 5 */ 6 7#define DISABLE_SIGN_COMPARE_WARNINGS 8 9#include "git-compat-util.h" 10#include "attr.h" 11#include "strbuf.h" 12#include "ws.h" 13 14unsigned whitespace_rule_cfg = WS_DEFAULT_RULE; 15 16static struct whitespace_rule { 17 const char *rule_name; 18 unsigned rule_bits; 19 unsigned loosens_error:1, 20 exclude_default:1; 21} whitespace_rule_names[] = { 22 { "trailing-space", WS_TRAILING_SPACE, 0 }, 23 { "space-before-tab", WS_SPACE_BEFORE_TAB, 0 }, 24 { "indent-with-non-tab", WS_INDENT_WITH_NON_TAB, 0 }, 25 { "cr-at-eol", WS_CR_AT_EOL, 1 }, 26 { "blank-at-eol", WS_BLANK_AT_EOL, 0 }, 27 { "blank-at-eof", WS_BLANK_AT_EOF, 0 }, 28 { "tab-in-indent", WS_TAB_IN_INDENT, 0, 1 }, 29}; 30 31unsigned parse_whitespace_rule(const char *string) 32{ 33 unsigned rule = WS_DEFAULT_RULE; 34 35 while (string) { 36 int i; 37 size_t len; 38 const char *ep; 39 const char *arg; 40 int negated = 0; 41 42 string = string + strspn(string, ", \t\n\r"); 43 ep = strchrnul(string, ','); 44 len = ep - string; 45 46 if (*string == '-') { 47 negated = 1; 48 string++; 49 len--; 50 } 51 if (!len) 52 break; 53 for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) { 54 if (strncmp(whitespace_rule_names[i].rule_name, 55 string, len)) 56 continue; 57 if (negated) 58 rule &= ~whitespace_rule_names[i].rule_bits; 59 else 60 rule |= whitespace_rule_names[i].rule_bits; 61 break; 62 } 63 if (skip_prefix(string, "tabwidth=", &arg)) { 64 unsigned tabwidth = atoi(arg); 65 if (0 < tabwidth && tabwidth < 0100) { 66 rule &= ~WS_TAB_WIDTH_MASK; 67 rule |= tabwidth; 68 } 69 else 70 warning("tabwidth %.*s out of range", 71 (int)(ep - arg), arg); 72 } 73 string = ep; 74 } 75 76 if (rule & WS_TAB_IN_INDENT && rule & WS_INDENT_WITH_NON_TAB) 77 die("cannot enforce both tab-in-indent and indent-with-non-tab"); 78 return rule; 79} 80 81unsigned whitespace_rule(struct index_state *istate, const char *pathname) 82{ 83 static struct attr_check *attr_whitespace_rule; 84 const char *value; 85 86 if (!attr_whitespace_rule) 87 attr_whitespace_rule = attr_check_initl("whitespace", NULL); 88 89 git_check_attr(istate, pathname, attr_whitespace_rule); 90 value = attr_whitespace_rule->items[0].value; 91 if (ATTR_TRUE(value)) { 92 /* true (whitespace) */ 93 unsigned all_rule = ws_tab_width(whitespace_rule_cfg); 94 int i; 95 for (i = 0; i < ARRAY_SIZE(whitespace_rule_names); i++) 96 if (!whitespace_rule_names[i].loosens_error && 97 !whitespace_rule_names[i].exclude_default) 98 all_rule |= whitespace_rule_names[i].rule_bits; 99 return all_rule; 100 } else if (ATTR_FALSE(value)) { 101 /* false (-whitespace) */ 102 return ws_tab_width(whitespace_rule_cfg); 103 } else if (ATTR_UNSET(value)) { 104 /* reset to default (!whitespace) */ 105 return whitespace_rule_cfg; 106 } else { 107 /* string */ 108 return parse_whitespace_rule(value); 109 } 110} 111 112/* The returned string should be freed by the caller. */ 113char *whitespace_error_string(unsigned ws) 114{ 115 struct strbuf err = STRBUF_INIT; 116 if ((ws & WS_TRAILING_SPACE) == WS_TRAILING_SPACE) 117 strbuf_addstr(&err, "trailing whitespace"); 118 else { 119 if (ws & WS_BLANK_AT_EOL) 120 strbuf_addstr(&err, "trailing whitespace"); 121 if (ws & WS_BLANK_AT_EOF) { 122 if (err.len) 123 strbuf_addstr(&err, ", "); 124 strbuf_addstr(&err, "new blank line at EOF"); 125 } 126 } 127 if (ws & WS_SPACE_BEFORE_TAB) { 128 if (err.len) 129 strbuf_addstr(&err, ", "); 130 strbuf_addstr(&err, "space before tab in indent"); 131 } 132 if (ws & WS_INDENT_WITH_NON_TAB) { 133 if (err.len) 134 strbuf_addstr(&err, ", "); 135 strbuf_addstr(&err, "indent with spaces"); 136 } 137 if (ws & WS_TAB_IN_INDENT) { 138 if (err.len) 139 strbuf_addstr(&err, ", "); 140 strbuf_addstr(&err, "tab in indent"); 141 } 142 return strbuf_detach(&err, NULL); 143} 144 145/* If stream is non-NULL, emits the line after checking. */ 146static unsigned ws_check_emit_1(const char *line, int len, unsigned ws_rule, 147 FILE *stream, const char *set, 148 const char *reset, const char *ws) 149{ 150 unsigned result = 0; 151 int written = 0; 152 int trailing_whitespace = -1; 153 int trailing_newline = 0; 154 int trailing_carriage_return = 0; 155 int i; 156 157 /* Logic is simpler if we temporarily ignore the trailing newline. */ 158 if (len > 0 && line[len - 1] == '\n') { 159 trailing_newline = 1; 160 len--; 161 } 162 if ((ws_rule & WS_CR_AT_EOL) && 163 len > 0 && line[len - 1] == '\r') { 164 trailing_carriage_return = 1; 165 len--; 166 } 167 168 /* Check for trailing whitespace. */ 169 if (ws_rule & WS_BLANK_AT_EOL) { 170 for (i = len - 1; i >= 0; i--) { 171 if (isspace(line[i])) { 172 trailing_whitespace = i; 173 result |= WS_BLANK_AT_EOL; 174 } 175 else 176 break; 177 } 178 } 179 180 if (trailing_whitespace == -1) 181 trailing_whitespace = len; 182 183 /* Check indentation */ 184 for (i = 0; i < trailing_whitespace; i++) { 185 if (line[i] == ' ') 186 continue; 187 if (line[i] != '\t') 188 break; 189 if ((ws_rule & WS_SPACE_BEFORE_TAB) && written < i) { 190 result |= WS_SPACE_BEFORE_TAB; 191 if (stream) { 192 fputs(ws, stream); 193 fwrite(line + written, i - written, 1, stream); 194 fputs(reset, stream); 195 fwrite(line + i, 1, 1, stream); 196 } 197 } else if (ws_rule & WS_TAB_IN_INDENT) { 198 result |= WS_TAB_IN_INDENT; 199 if (stream) { 200 fwrite(line + written, i - written, 1, stream); 201 fputs(ws, stream); 202 fwrite(line + i, 1, 1, stream); 203 fputs(reset, stream); 204 } 205 } else if (stream) { 206 fwrite(line + written, i - written + 1, 1, stream); 207 } 208 written = i + 1; 209 } 210 211 /* Check for indent using non-tab. */ 212 if ((ws_rule & WS_INDENT_WITH_NON_TAB) && i - written >= ws_tab_width(ws_rule)) { 213 result |= WS_INDENT_WITH_NON_TAB; 214 if (stream) { 215 fputs(ws, stream); 216 fwrite(line + written, i - written, 1, stream); 217 fputs(reset, stream); 218 } 219 written = i; 220 } 221 222 if (stream) { 223 /* 224 * Now the rest of the line starts at "written". 225 * The non-highlighted part ends at "trailing_whitespace". 226 */ 227 228 /* Emit non-highlighted (middle) segment. */ 229 if (trailing_whitespace - written > 0) { 230 fputs(set, stream); 231 fwrite(line + written, 232 trailing_whitespace - written, 1, stream); 233 fputs(reset, stream); 234 } 235 236 /* Highlight errors in trailing whitespace. */ 237 if (trailing_whitespace != len) { 238 fputs(ws, stream); 239 fwrite(line + trailing_whitespace, 240 len - trailing_whitespace, 1, stream); 241 fputs(reset, stream); 242 } 243 if (trailing_carriage_return) 244 fputc('\r', stream); 245 if (trailing_newline) 246 fputc('\n', stream); 247 } 248 return result; 249} 250 251void ws_check_emit(const char *line, int len, unsigned ws_rule, 252 FILE *stream, const char *set, 253 const char *reset, const char *ws) 254{ 255 (void)ws_check_emit_1(line, len, ws_rule, stream, set, reset, ws); 256} 257 258unsigned ws_check(const char *line, int len, unsigned ws_rule) 259{ 260 return ws_check_emit_1(line, len, ws_rule, NULL, NULL, NULL, NULL); 261} 262 263int ws_blank_line(const char *line, int len) 264{ 265 /* 266 * We _might_ want to treat CR differently from other 267 * whitespace characters when ws_rule has WS_CR_AT_EOL, but 268 * for now we just use this stupid definition. 269 */ 270 while (len-- > 0) { 271 if (!isspace(*line)) 272 return 0; 273 line++; 274 } 275 return 1; 276} 277 278/* Copy the line onto the end of the strbuf while fixing whitespaces */ 279void ws_fix_copy(struct strbuf *dst, const char *src, int len, unsigned ws_rule, int *error_count) 280{ 281 /* 282 * len is number of bytes to be copied from src, starting 283 * at src. Typically src[len-1] is '\n', unless this is 284 * the incomplete last line. 285 */ 286 int i; 287 int add_nl_to_tail = 0; 288 int add_cr_to_tail = 0; 289 int fixed = 0; 290 int last_tab_in_indent = -1; 291 int last_space_in_indent = -1; 292 int need_fix_leading_space = 0; 293 294 /* 295 * Strip trailing whitespace 296 */ 297 if (ws_rule & WS_BLANK_AT_EOL) { 298 if (0 < len && src[len - 1] == '\n') { 299 add_nl_to_tail = 1; 300 len--; 301 if (0 < len && src[len - 1] == '\r') { 302 add_cr_to_tail = !!(ws_rule & WS_CR_AT_EOL); 303 len--; 304 } 305 } 306 if (0 < len && isspace(src[len - 1])) { 307 while (0 < len && isspace(src[len-1])) 308 len--; 309 fixed = 1; 310 } 311 } 312 313 /* 314 * Check leading whitespaces (indent) 315 */ 316 for (i = 0; i < len; i++) { 317 char ch = src[i]; 318 if (ch == '\t') { 319 last_tab_in_indent = i; 320 if ((ws_rule & WS_SPACE_BEFORE_TAB) && 321 0 <= last_space_in_indent) 322 need_fix_leading_space = 1; 323 } else if (ch == ' ') { 324 last_space_in_indent = i; 325 if ((ws_rule & WS_INDENT_WITH_NON_TAB) && 326 ws_tab_width(ws_rule) <= i - last_tab_in_indent) 327 need_fix_leading_space = 1; 328 } else 329 break; 330 } 331 332 if (need_fix_leading_space) { 333 /* Process indent ourselves */ 334 int consecutive_spaces = 0; 335 int last = last_tab_in_indent + 1; 336 337 if (ws_rule & WS_INDENT_WITH_NON_TAB) { 338 /* have "last" point at one past the indent */ 339 if (last_tab_in_indent < last_space_in_indent) 340 last = last_space_in_indent + 1; 341 else 342 last = last_tab_in_indent + 1; 343 } 344 345 /* 346 * between src[0..last-1], strip the funny spaces, 347 * updating them to tab as needed. 348 */ 349 for (i = 0; i < last; i++) { 350 char ch = src[i]; 351 if (ch != ' ') { 352 consecutive_spaces = 0; 353 strbuf_addch(dst, ch); 354 } else { 355 consecutive_spaces++; 356 if (consecutive_spaces == ws_tab_width(ws_rule)) { 357 strbuf_addch(dst, '\t'); 358 consecutive_spaces = 0; 359 } 360 } 361 } 362 while (0 < consecutive_spaces--) 363 strbuf_addch(dst, ' '); 364 len -= last; 365 src += last; 366 fixed = 1; 367 } else if ((ws_rule & WS_TAB_IN_INDENT) && last_tab_in_indent >= 0) { 368 /* Expand tabs into spaces */ 369 int start = dst->len; 370 int last = last_tab_in_indent + 1; 371 for (i = 0; i < last; i++) { 372 if (src[i] == '\t') 373 do { 374 strbuf_addch(dst, ' '); 375 } while ((dst->len - start) % ws_tab_width(ws_rule)); 376 else 377 strbuf_addch(dst, src[i]); 378 } 379 len -= last; 380 src += last; 381 fixed = 1; 382 } 383 384 strbuf_add(dst, src, len); 385 if (add_cr_to_tail) 386 strbuf_addch(dst, '\r'); 387 if (add_nl_to_tail) 388 strbuf_addch(dst, '\n'); 389 if (fixed && error_count) 390 (*error_count)++; 391}