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