mirror of
https://chromium.googlesource.com/chromium/tools/depot_tools.git
synced 2026-01-11 18:51:29 +00:00
presubmit: add location data to presubmit result
This will help us to display preusbmit result as findings both in Gerrit and in Cider workspaces. Change-Id: I0f5a9c503c1b14dc8dcbf9794cc556ef215a46a2 Bug: 404837554 Reviewed-on: https://chromium-review.googlesource.com/c/chromium/tools/depot_tools/+/6469939 Commit-Queue: Yiwei Zhang <yiwzhang@google.com> Reviewed-by: Gavin Mak <gavinmak@google.com>
This commit is contained in:
@@ -36,7 +36,8 @@ import unittest # Exposed through the API.
|
||||
import urllib.parse as urlparse
|
||||
import urllib.request as urllib_request
|
||||
import urllib.error as urllib_error
|
||||
from typing import Mapping
|
||||
from dataclasses import asdict, dataclass
|
||||
from typing import ClassVar, Mapping
|
||||
from warnings import warn
|
||||
|
||||
# Local imports.
|
||||
@@ -311,6 +312,67 @@ def prompt_should_continue(prompt_string):
|
||||
return response in ('y', 'yes')
|
||||
|
||||
|
||||
# Top level object so multiprocessing can pickle
|
||||
# Public access through OutputApi object.
|
||||
@dataclass
|
||||
class _PresubmitResultLocation:
|
||||
COMMIT_MSG_PATH: ClassVar[str] = '/COMMIT_MSG'
|
||||
# path to the file where errors/warnings are reported.
|
||||
#
|
||||
# path MUST either be COMMIT_MSG_PATH or relative to the repo root to
|
||||
# indicate the errors/warnings are against the commit message
|
||||
# (a.k.a cl description).
|
||||
file_path: str
|
||||
# The range in the file defined by (start_line, start_col) -
|
||||
# (end_line, end_col) where errors/warnings are reported.
|
||||
# The semantic are the same as Gerrit comment range:
|
||||
# https://gerrit-review.googlesource.com/Documentation/rest-api-changes.html#comment-range
|
||||
#
|
||||
# To specify the entire line, make start_line == end_line and
|
||||
# start_col == end_col == 0.
|
||||
start_line: int = 0 # inclusive 1-based
|
||||
start_col: int = 0 # inclusive 0-based
|
||||
end_line: int = 0 # exclusive 1-based
|
||||
end_col: int = 0 # exclusive 0-based
|
||||
|
||||
def validate(self):
|
||||
if not self.file_path:
|
||||
raise ValueError('file path is required')
|
||||
if self.file_path != self.COMMIT_MSG_PATH and os.path.isabs(
|
||||
self.file_path):
|
||||
raise ValueError(
|
||||
f'file path must be relative path, got {self.file_path}')
|
||||
if not self.start_line:
|
||||
if self.end_line:
|
||||
raise ValueError('end_line must be empty if start line is not '
|
||||
'specified')
|
||||
if self.start_col:
|
||||
raise ValueError('start_col must be empty if start line is not '
|
||||
'specified')
|
||||
if self.end_col:
|
||||
raise ValueError('end_col must be empty if start line is not '
|
||||
'specified')
|
||||
elif self.start_line < 0:
|
||||
raise ValueError('start_line MUST not be negative, '
|
||||
f'got {self.start_line}')
|
||||
elif self.end_line < 1:
|
||||
raise ValueError('start_line is specified so end_line must be '
|
||||
f'positive, got {self.end_line}')
|
||||
elif self.start_col < 0:
|
||||
raise ValueError('start_col MUST not be negative, '
|
||||
f'got {self.start_col}')
|
||||
elif self.end_col < 0:
|
||||
raise ValueError('end_col MUST not be negative, '
|
||||
f'got {self.end_col}')
|
||||
elif self.start_line > self.end_line or (
|
||||
self.start_line == self.end_line
|
||||
and self.start_col > self.end_col and self.end_col > 0):
|
||||
raise ValueError(
|
||||
'(start_line, start_col) must not be after (end_line, end_col'
|
||||
f'), got ({self.start_line}, {self.start_col}) .. '
|
||||
f'({self.end_line}, {self.end_col})')
|
||||
|
||||
|
||||
# Top level object so multiprocessing can pickle
|
||||
# Public access through OutputApi object.
|
||||
class _PresubmitResult(object):
|
||||
@@ -318,15 +380,28 @@ class _PresubmitResult(object):
|
||||
fatal = False
|
||||
should_prompt = False
|
||||
|
||||
def __init__(self, message, items=None, long_text='', show_callstack=None):
|
||||
"""
|
||||
message: A short one-line message to indicate errors.
|
||||
items: A list of short strings to indicate where errors occurred.
|
||||
long_text: multi-line text output, e.g. from another tool
|
||||
def __init__(self,
|
||||
message: str,
|
||||
items: list[str] = None,
|
||||
long_text: str = '',
|
||||
locations: list[_PresubmitResultLocation] = None,
|
||||
show_callstack: bool = None):
|
||||
"""Inits _PresubmitResult.
|
||||
|
||||
Args:
|
||||
message: A short one-line message to indicate errors.
|
||||
items: A list of short strings to indicate where errors occurred.
|
||||
Note that if you are using this parameter to print where errors
|
||||
occurred, please use `locations` instead
|
||||
long_text: multi-line text output, e.g. from another tool
|
||||
locations: The locations indicate where the errors occurred.
|
||||
"""
|
||||
self._message = _PresubmitResult._ensure_str(message)
|
||||
self._items = items or []
|
||||
self._long_text = _PresubmitResult._ensure_str(long_text.rstrip())
|
||||
self._locations = locations or []
|
||||
for loc in self._locations:
|
||||
loc.validate()
|
||||
if show_callstack is None:
|
||||
show_callstack = _SHOW_CALLSTACKS
|
||||
if show_callstack:
|
||||
@@ -346,24 +421,45 @@ class _PresubmitResult(object):
|
||||
return val.decode()
|
||||
raise ValueError("Unknown string type %s" % type(val))
|
||||
|
||||
def handle(self):
|
||||
sys.stdout.write(self._message)
|
||||
sys.stdout.write('\n')
|
||||
def handle(self, out_file=None):
|
||||
if not out_file:
|
||||
out_file = sys.stdout
|
||||
out_file.write(self._message)
|
||||
out_file.write('\n')
|
||||
for item in self._items:
|
||||
sys.stdout.write(' ')
|
||||
out_file.write(' ')
|
||||
# Write separately in case it's unicode.
|
||||
sys.stdout.write(str(item))
|
||||
sys.stdout.write('\n')
|
||||
out_file.write(str(item))
|
||||
out_file.write('\n')
|
||||
if self._locations:
|
||||
out_file.write('Found in:\n')
|
||||
for loc in self._locations:
|
||||
if loc.file_path == _PresubmitResultLocation.COMMIT_MSG_PATH:
|
||||
out_file.write(' - Commit Message')
|
||||
else:
|
||||
out_file.write(f' - {loc.file_path}')
|
||||
if not loc.start_line:
|
||||
pass
|
||||
elif loc.start_line == loc.end_line and (loc.start_col == 0
|
||||
and loc.end_col == 0):
|
||||
out_file.write(f' [Ln {loc.start_line}]')
|
||||
elif loc.start_col == 0 and loc.end_col == 0:
|
||||
out_file.write(f' [Ln {loc.start_line} - {loc.end_line}]')
|
||||
else:
|
||||
out_file.write(f' [Ln {loc.start_line}, Col {loc.start_col}'
|
||||
f' - Ln {loc.end_line}, Col {loc.end_col}]')
|
||||
out_file.write('\n')
|
||||
if self._long_text:
|
||||
sys.stdout.write('\n***************\n')
|
||||
out_file.write('\n***************\n')
|
||||
# Write separately in case it's unicode.
|
||||
sys.stdout.write(self._long_text)
|
||||
sys.stdout.write('\n***************\n')
|
||||
out_file.write(self._long_text)
|
||||
out_file.write('\n***************\n')
|
||||
|
||||
def json_format(self):
|
||||
return {
|
||||
'message': self._message,
|
||||
'items': [str(item) for item in self._items],
|
||||
'locations': [asdict(loc) for loc in self._locations],
|
||||
'long_text': self._long_text,
|
||||
'fatal': self.fatal
|
||||
}
|
||||
@@ -515,6 +611,7 @@ class OutputApi(object):
|
||||
PresubmitPromptWarning = _PresubmitPromptWarning
|
||||
PresubmitNotifyResult = _PresubmitNotifyResult
|
||||
MailTextResult = _MailTextResult
|
||||
PresubmitResultLocation = _PresubmitResultLocation
|
||||
|
||||
def __init__(self, is_committing):
|
||||
self.is_committing = is_committing
|
||||
|
||||
Reference in New Issue
Block a user