def parse(contents: str) -> Set[Problem]: result = set() # type: Set[Problem] for line in contents.splitlines(): match = SWIFTLINT_LINE_REGEX.match(line) if match: groups = match.groupdict() result.add( Problem(os.path.relpath(groups['path']), groups['line'], groups['message'])) return result
def parse(contents: str) -> Set[Problem]: result = set() # type: Set[Problem] for line in contents.splitlines(): match = PYLINT_LINE_REGEX.match(line) if match: groups = match.groupdict() path = groups['path'] if path.startswith('./'): path = path[2:] result.add(Problem(path, groups['line'], groups['message'])) return result
def parse(contents: str) -> Set[Problem]: result = set() # type: Set[Problem] for line in contents.splitlines(): match = MYPY_LINE_REGEX.match(line) if match: groups = match.groupdict() if groups['code'] != 'note': result.add( Problem(groups['path'], groups['line'], '{}: {}'.format(groups['code'], groups['message']))) return result
def parse(contents: str) -> Set[Problem]: result = set() # type: Set[Problem] try: root = ElementTree.fromstring(contents) except ElementTree.ParseError: return result for file in root.findall('file'): file_name = file.get('name') for error in file.findall('error'): result.add( Problem( file_name, error.get('line'), '{}: {}'.format(error.get('source'), error.get('message')))) return result
async def get_existing_problems(self) -> Set[Problem]: note_ref = '' result = set() if self.remote: fetch_notes = await asyncio.create_subprocess_exec( 'git', 'fetch', self.remote, '{0}:{0}'.format(NOTES_REF), **GIT_SUBPROCESS_KWARGS) await fetch_notes.wait() last_n_revisions_proc = await asyncio.create_subprocess_exec( 'git', 'log', '--skip=1', '-{}'.format(MAX_REVISIONS), '--pretty=%H', **GIT_SUBPROCESS_KWARGS) async for line in last_n_revisions_proc.stdout: notes_ref_proc = await asyncio.create_subprocess_exec( 'git', 'notes', 'list', line.decode().strip(), **GIT_SUBPROCESS_KWARGS) await notes_ref_proc.wait() if notes_ref_proc.returncode == 0: note_ref = ( await notes_ref_proc.stdout.readline()).decode().strip() if note_ref: break await last_n_revisions_proc.wait() if note_ref: notes_proc = await asyncio.create_subprocess_exec( 'git', 'show', note_ref, **GIT_SUBPROCESS_KWARGS) async for line in notes_proc.stdout: try: problems = json.loads(line.decode()) for problem in problems: result.add(Problem.from_json(problem)) except Exception: pass await notes_proc.wait() return result
async def report(self, problems: List[Problem]) -> None: grouped_problems = Problem.group_by_path_and_line(problems) headers = { 'Authorization': 'token {}'.format(self.auth_token), } with aiohttp.ClientSession(headers=headers) as client_session: (line_map, existing_messages) = await asyncio.gather( self.create_line_to_position_map(client_session), self.get_existing_messages(client_session)) lint_errors = 0 review_comment_awaitable = [] pr_url = self._get_pr_url() no_matching_line_number = [] for location, problems_for_line in grouped_problems: message_for_line = [ COMMENT_HEADER and '{}:'.format(COMMENT_HEADER) or '', ''] reported_problems_for_line = set() path = location[0] line_number = location[1] position = line_map.get(path, {}).get(line_number, None) if position is None and path in line_map and REPORT_CLOSEST: file_map = line_map[path] closest_line = min(file_map.keys(), key=lambda x: abs(x - line_number)) position = file_map[closest_line] message_for_line.append('(From line {})'.format( line_number)) message_for_line.append('```') if position is not None: for problem in problems_for_line: if problem.message not in reported_problems_for_line: message_for_line.append(problem.message) reported_problems_for_line.add(problem.message) message_for_line.append('```') message = '\n'.join(message_for_line) if (path, position, message) not in existing_messages: lint_errors += 1 if lint_errors <= MAX_LINT_ERROR_REPORTS: data = json.dumps({ 'body': message, 'commit_id': self.commit, 'path': path, 'position': position, }, sort_keys=True) review_comment_awaitable.append( client_session.post(pr_url, data=data)) else: no_matching_line_number.append((location, problems_for_line)) if lint_errors > MAX_LINT_ERROR_REPORTS: message = '''{0} Too many lint errors to report inline! {1} lines have a problem. Only reporting the first {2}.'''.format( COMMENT_HEADER and '{}:'.format(COMMENT_HEADER) or '', lint_errors, MAX_LINT_ERROR_REPORTS) data = json.dumps({ 'body': message }) review_comment_awaitable.append( asyncio.ensure_future(client_session.post( self._get_issue_url(), data=data))) if no_matching_line_number and REPORT_NO_MATCHING: no_matching_line_messages = [] for location, problems_for_line in no_matching_line_number: path = location[0] line_number = location[1] no_matching_line_messages.append( '{0}:{1}:'.format(path, line_number)) for problem in problems_for_line: no_matching_line_messages.append('\t{0}'.format( problem.message)) message = ('Linters found some problems with lines not ' 'modified by this commit:\n```\n{0}\n```'.format( '\n'.join(no_matching_line_messages))) data = json.dumps({ 'body': message }) review_comment_awaitable.append( asyncio.ensure_future(client_session.post( self._get_issue_url(), data=data))) responses = await asyncio.gather( *review_comment_awaitable ) # type: List[aiohttp.ClientResponse] for response in responses: response.close()
async def report(self, problems: List[Problem]) -> None: grouped_problems = Problem.group_by_path_and_line(problems) headers = { 'Authorization': 'token {}'.format(self.auth_token), } with aiohttp.ClientSession(headers=headers) as client_session: (line_map, existing_messages) = await asyncio.gather( self.create_line_to_position_map(client_session), self.get_existing_messages(client_session)) lint_errors = 0 review_comment_awaitable = [] pr_url = self._get_pr_url() no_matching_line_number = [] for location, problems_for_line in grouped_problems: message_for_line = [ COMMENT_HEADER and '{}:'.format(COMMENT_HEADER) or '', '' ] reported_problems_for_line = set() path = location[0] line_number = location[1] position = line_map.get(path, {}).get(line_number, None) if position is None and path in line_map and REPORT_CLOSEST: file_map = line_map[path] closest_line = min(file_map.keys(), key=lambda x: abs(x - line_number)) position = file_map[closest_line] message_for_line.append( '(From line {})'.format(line_number)) message_for_line.append('```') if position is not None: for problem in problems_for_line: if problem.message not in reported_problems_for_line: message_for_line.append(problem.message) reported_problems_for_line.add(problem.message) message_for_line.append('```') message = '\n'.join(message_for_line) if (path, position, message) not in existing_messages: lint_errors += 1 if lint_errors <= MAX_LINT_ERROR_REPORTS: data = json.dumps( { 'body': message, 'commit_id': self.commit, 'path': path, 'position': position, }, sort_keys=True) review_comment_awaitable.append( client_session.post(pr_url, data=data)) else: no_matching_line_number.append( (location, problems_for_line)) if lint_errors > MAX_LINT_ERROR_REPORTS: message = '''{0} Too many lint errors to report inline! {1} lines have a problem. Only reporting the first {2}.'''.format( COMMENT_HEADER and '{}:'.format(COMMENT_HEADER) or '', lint_errors, MAX_LINT_ERROR_REPORTS) data = json.dumps({'body': message}) review_comment_awaitable.append( asyncio.ensure_future( client_session.post(self._get_issue_url(), data=data))) if no_matching_line_number and REPORT_NO_MATCHING: no_matching_line_messages = [] for location, problems_for_line in no_matching_line_number: path = location[0] line_number = location[1] no_matching_line_messages.append('{0}:{1}:'.format( path, line_number)) for problem in problems_for_line: no_matching_line_messages.append('\t{0}'.format( problem.message)) message = ('Linters found some problems with lines not ' 'modified by this commit:\n```\n{0}\n```'.format( '\n'.join(no_matching_line_messages))) data = json.dumps({'body': message}) review_comment_awaitable.append( asyncio.ensure_future( client_session.post(self._get_issue_url(), data=data))) responses = await asyncio.gather( *review_comment_awaitable ) # type: List[aiohttp.ClientResponse] for response in responses: response.close()