• Home
  • Line#
  • Scopes#
  • Navigate#
  • Raw
  • Download
1# Copyright 2022 The Pigweed Authors
2#
3# Licensed under the Apache License, Version 2.0 (the "License"); you may not
4# use this file except in compliance with the License. You may obtain a copy of
5# the License at
6#
7#     https://www.apache.org/licenses/LICENSE-2.0
8#
9# Unless required by applicable law or agreed to in writing, software
10# distributed under the License is distributed on an "AS IS" BASIS, WITHOUT
11# WARRANTIES OR CONDITIONS OF ANY KIND, either express or implied. See the
12# License for the specific language governing permissions and limitations under
13# the License.
14"""Check the formatting of TODOs."""
15
16import logging
17from pathlib import Path
18import re
19from typing import Iterable, Pattern, Sequence, Union
20
21from pw_presubmit import PresubmitContext, filter_paths
22
23_LOG: logging.Logger = logging.getLogger(__name__)
24
25EXCLUDE: Sequence[str] = (
26    # Metadata
27    r'^docker/tag$',
28    r'\byarn.lock$',
29    # Data files
30    r'\.bin$',
31    r'\.csv$',
32    r'\.elf$',
33    r'\.gif$',
34    r'\.jpg$',
35    r'\.json$',
36    r'\.png$',
37    r'\.svg$',
38    r'\.xml$',
39)
40
41# todo-check: disable
42BUGS_ONLY = re.compile(r'\bTODO\(b/\d+(?:, ?b/\d+)*\).*\w')
43BUGS_OR_USERNAMES = re.compile(
44    r'\bTODO\((?:b/\d+|[a-z]+)(?:, ?(?:b/\d+|[a-z]+))*\).*\w'
45)
46_TODO = re.compile(r'\bTODO\b')
47# todo-check: enable
48
49# If seen, ignore this line and the next.
50_IGNORE = 'todo-check: ignore'
51
52# Ignore a whole section. Please do not change the order of these lines.
53_DISABLE = 'todo-check: disable'
54_ENABLE = 'todo-check: enable'
55
56
57def _process_file(ctx: PresubmitContext, todo_pattern: re.Pattern, path: Path):
58    with path.open() as ins:
59        _LOG.debug('Evaluating path %s', path)
60        enabled = True
61        prev = ''
62
63        try:
64            summary = []
65            for i, line in enumerate(ins, 1):
66                if _DISABLE in line:
67                    enabled = False
68                elif _ENABLE in line:
69                    enabled = True
70
71                if not enabled or _IGNORE in line or _IGNORE in prev:
72                    prev = line
73                    continue
74
75                if _TODO.search(line):
76                    if not todo_pattern.search(line):
77                        # todo-check: ignore
78                        ctx.fail(f'Bad TODO on line {i}:', path)
79                        ctx.fail(f'    {line.strip()}')
80                        summary.append(
81                            f'{path.relative_to(ctx.root)}:{i}:{line.strip()}'
82                        )
83
84                prev = line
85
86            if summary:
87                with ctx.failure_summary_log.open('w') as outs:
88                    for line in summary:
89                        print(line, file=outs)
90
91        except UnicodeDecodeError:
92            # File is not text, like a gif.
93            _LOG.debug('File %s is not a text file', path)
94
95
96def create(
97    todo_pattern: re.Pattern = BUGS_ONLY,
98    exclude: Iterable[Union[Pattern[str], str]] = EXCLUDE,
99):
100    """Create a todo_check presubmit step that uses the given pattern."""
101
102    @filter_paths(exclude=exclude)
103    def todo_check(ctx: PresubmitContext):
104        """Check that TODO lines are valid."""  # todo-check: ignore
105        for path in ctx.paths:
106            _process_file(ctx, todo_pattern, path)
107
108    return todo_check
109