@ -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