Coverage Report

Created: 2025-12-31 07:02

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