Skip to content
This repository was archived by the owner on Oct 6, 2023. It is now read-only.
Draft
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension


Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
2 changes: 1 addition & 1 deletion .github/workflows/test.yaml
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@ jobs:
runs-on: ubuntu-latest
strategy:
matrix:
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9]
python-version: [2.7, 3.5, 3.6, 3.7, 3.8, 3.9, '3.10']
steps:
- uses: actions/checkout@v2
- name: Set up Python ${{ matrix.python-version }}
Expand Down
2 changes: 1 addition & 1 deletion lintly/backends/base.py
Original file line number Diff line number Diff line change
Expand Up @@ -39,7 +39,7 @@ def get_pr_diff(self, pr):
"""
raise NotImplementedError

def create_pull_request_review(self, pr, patch, all_violations, pr_review_action):
def create_pull_request_review(self, pr, patch, all_violations, pr_review_action, has_body):
"""
Creates a pull request review for the given build.
"""
Expand Down
2 changes: 1 addition & 1 deletion lintly/backends/dummy.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@ def create_pull_request_comment(self, pr, comment):
def delete_pull_request_comments(self, pr):
pass

def create_pull_request_review(self, pr, patch, all_violations, pr_review_action):
def create_pull_request_review(self, pr, patch, all_violations, pr_review_action, has_body):
pass

def delete_pull_request_review_comments(self, pr):
Expand Down
9 changes: 5 additions & 4 deletions lintly/backends/github.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@

from github import GithubException, UnknownObjectException, Github

from lintly.constants import LINTLY_IDENTIFIER
from lintly.config import Config
from lintly.formatters import (
build_pr_review_line_comment,
build_pr_review_body,
Expand Down Expand Up @@ -124,7 +124,7 @@ def __init__(self, token, project, context):
self.context = context

def _should_delete_comment(self, comment):
return LINTLY_IDENTIFIER in comment.body
return Config.LINTLY_IDENTIFIER in comment.body

@translate_github_exception
def get_pull_request(self, pr):
Expand Down Expand Up @@ -180,7 +180,7 @@ def _get_event(self, review_action):
elif review_action == ACTION_REVIEW_APPROVE:
return 'APPROVE'

def create_pull_request_review(self, pr, patch, all_violations, pr_review_action):
def create_pull_request_review(self, pr, patch, all_violations, pr_review_action, has_body):
comments = []
for file_path in all_violations:
violations = all_violations[file_path]
Expand Down Expand Up @@ -209,10 +209,11 @@ def create_pull_request_review(self, pr, patch, all_violations, pr_review_action
# there are no pending comments to add then we send the request
if len(comments_batch) == GITHUB_PULL_REQUEST_COMMENT_LIMIT or not comments:
data = {
'body': build_pr_review_body(all_violations),
'event': self._get_event(pr_review_action),
'comments': comments_batch,
}
if has_body:
data['body'] = build_pr_review_body(all_violations)

url = '/repos/{owner}/{repo_name}/pulls/{pr_number}/reviews'.format(
owner=self.project.owner_login,
Expand Down
6 changes: 3 additions & 3 deletions lintly/backends/gitlab.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,7 @@
import gitlab
import requests

from lintly.constants import LINTLY_IDENTIFIER
from lintly.config import Config

from .base import BaseGitBackend
from .errors import (
Expand Down Expand Up @@ -136,14 +136,14 @@ def delete_pull_request_comments(self, pr):
mr = project.mergerequests.list(iid=pr)[0]
client = GitLabAPIClient(self.token, self.user, self.project)
for note in mr.notes.list(all=True, per_page=DEFAULT_PER_PAGE):
if LINTLY_IDENTIFIER in note.body:
if Config.LINTLY_IDENTIFIER in note.body:
url = '/projects/{project_id}/merge_requests/{mr_id}/notes/{note_id}'.format(
project_id=project.id, mr_id=mr.id, note_id=note.id
)
client.delete(url)

@translate_gitlab_exception
def create_pull_request_review(self, pr, patch, all_violations, pr_review_action):
def create_pull_request_review(self, pr, patch, all_violations, pr_review_action, has_body):
raise NotSupportedError()

@translate_gitlab_exception
Expand Down
3 changes: 2 additions & 1 deletion lintly/builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -164,7 +164,8 @@ def submit_pr_review(self, patch, pr_review_action):
self.config.pr,
patch,
self._diff_violations,
pr_review_action
pr_review_action,
self.config.review_body
)
post_pr_comment = False
except GitClientError as e:
Expand Down
10 changes: 10 additions & 0 deletions lintly/cli.py
Original file line number Diff line number Diff line change
Expand Up @@ -55,6 +55,16 @@
@click.option('--log',
is_flag=True,
help='Send Lintly debug logs to the console. Default false')
@click.option('--review-body/--no-review-body',
default=True,
help=('Whether Lintly should post a PR review with a body. '
'The body is not removed after a re-run.'))
@click.option('--comment-tag',
default='',
help='A tag used to identify comments from a previous run that should be deleted')
@click.option('--base-dir',
default='.',
help='The base git directory')
@click.option('--exit-zero/--no-exit-zero', default=False,
help=('Whether Lintly should exit with error code indicating '
'amount of violations or not. Default false'))
Expand Down
21 changes: 21 additions & 0 deletions lintly/config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,8 +7,14 @@
class Config(object):
"""A Config object that knows how to return configuration from the CLI or Continuous Integration services"""

LINTLY_IDENTIFIER = '<!-- Automatically posted by Lintly -->'
BASE_DIR = '.'

def __init__(self, cli_config):
self.cli_config = cli_config
if self.comment_tag:
type(self).LINTLY_IDENTIFIER = '(%s)%s' % (self.comment_tag, type(self).LINTLY_IDENTIFIER)
type(self).BASE_DIR = self.base_dir

def as_dict(self):
return {
Expand All @@ -21,6 +27,9 @@ def as_dict(self):
'post_status': self.post_status,
'request_changes': self.request_changes,
'github_check_run_id': self.github_check_run_id,
'review_body': self.review_body,
'comment_tag': self.comment_tag,
'base_dir': self.base_dir,
}

@property
Expand Down Expand Up @@ -59,6 +68,18 @@ def post_status(self):
def request_changes(self):
return self.cli_config['request_changes']

@property
def review_body(self):
return self.cli_config['review_body']

@property
def comment_tag(self):
return self.cli_config['comment_tag']

@property
def base_dir(self):
return self.cli_config['base_dir']

@property
def github_check_run_id(self):
"""The Check Run ID from GitHub Actions.
Expand Down
5 changes: 0 additions & 5 deletions lintly/constants.py
Original file line number Diff line number Diff line change
@@ -1,11 +1,6 @@
FAIL_ON_ANY = 'any'
FAIL_ON_NEW = 'new'

# Identifies that a comment came from Lintly. This is used to aid in automatically
# deleting old PR comments/reviews. This is valid Markdown that is hidden from
# users in GitHub and GitLab.
LINTLY_IDENTIFIER = '<!-- Automatically posted by Lintly -->'

# These constants define the actions that lintly might take against a PR concerning reviews, ie.
# not the commit status
ACTION_REVIEW_USE_CHECKS = 'use_checks'
Expand Down
8 changes: 4 additions & 4 deletions lintly/formatters.py
Original file line number Diff line number Diff line change
Expand Up @@ -5,7 +5,7 @@

from jinja2 import Environment, FileSystemLoader

from .constants import LINTLY_IDENTIFIER
from .config import Config


TEMPLATES_PATH = os.path.join(os.path.dirname(__file__), 'templates')
Expand All @@ -23,7 +23,7 @@ def build_pr_comment(config, violations):
:return: The comment
"""
template = env.get_template('pr_comment.txt')
return template.render(violations=violations, LINTLY_IDENTIFIER=LINTLY_IDENTIFIER)
return template.render(violations=violations, LINTLY_IDENTIFIER=Config.LINTLY_IDENTIFIER)


def build_pr_review_line_comment(violation):
Expand All @@ -32,7 +32,7 @@ def build_pr_review_line_comment(violation):
:return: The comment
"""
template = env.get_template('pr_review_line_comment.txt')
return template.render(violation=violation, LINTLY_IDENTIFIER=LINTLY_IDENTIFIER)
return template.render(violation=violation, LINTLY_IDENTIFIER=Config.LINTLY_IDENTIFIER)


def build_check_line_comment(violation):
Expand All @@ -42,4 +42,4 @@ def build_check_line_comment(violation):

def build_pr_review_body(violations):
template = env.get_template('pr_review_body.txt')
return template.render(violations=violations, LINTLY_IDENTIFIER=LINTLY_IDENTIFIER)
return template.render(violations=violations, LINTLY_IDENTIFIER=Config.LINTLY_IDENTIFIER)
22 changes: 14 additions & 8 deletions lintly/parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,22 +8,23 @@
import re

from .violations import Violation
from .config import Config


class BaseLintParser(object):

def parse_violations(self, output):
raise NotImplementedError

def _get_working_dir(self):
return os.getcwd()

def _normalize_path(self, path):
"""
Normalizes a file path so that it returns a path relative to the root repo directory.
"""
norm_path = os.path.normpath(path)
return os.path.relpath(norm_path, start=self._get_working_dir())
rel_path = os.path.relpath(norm_path, start=Config.BASE_DIR)
if os.name == 'nt':
rel_path = rel_path.replace('\\', '/')
return rel_path


class LineRegexParser(BaseLintParser):
Expand Down Expand Up @@ -58,11 +59,12 @@ def parse_violations(self, output):

path = self._normalize_path(match.group('path'))

groups = match.groupdict()
violation = Violation(
line=int(match.group('line')),
column=int(match.group('column')),
code=match.group('code'),
message=match.group('message')
line=int(groups.get('line') or 1),
column=int(groups.get('column') or 1),
code=(groups.get('code') or '[empty]').strip(),
message=(groups.get('message') or '[empty]').strip()
)

violations[path].append(violation)
Expand Down Expand Up @@ -296,4 +298,8 @@ def parse_violations(self, output):

# cfn-nag JSON output
'cfn-nag': CfnNagParser(),

# Mypy formatter
'mypy': LineRegexParser(r'^(?P<path>.*\.py):(?:(?P<line>\d*):)?(?:(?P<column>\d*):)? [^:]*: '
r'(?P<message>.*?)(?:\[(?P<code>\S*)\])?$'),
}
6 changes: 3 additions & 3 deletions requirements.txt
Original file line number Diff line number Diff line change
@@ -1,12 +1,12 @@
Jinja2<3.0
Jinja2<4.0
PyGithub<2.0
cached-property<2.0
ci-py
click<7.0
click<9.0
codecov
coverage
flake8
mock
pytest
python-gitlab<2.0
python-gitlab<4.0
tox
6 changes: 3 additions & 3 deletions setup.py
Original file line number Diff line number Diff line change
Expand Up @@ -8,10 +8,10 @@
dependencies = [
'ci-py',
'cached-property<2.0',
'click<8.0',
'Jinja2<3.0',
'click<9.0',
'Jinja2<4.0',
'PyGithub<2.0',
'python-gitlab<2.0',
'python-gitlab<4.0',
'six',
]

Expand Down
2 changes: 2 additions & 0 deletions tests/test_builds.py
Original file line number Diff line number Diff line change
Expand Up @@ -31,6 +31,8 @@ def config(format_and_context):
"commit_sha": "xyz123notarealsha",
"context": format_and_context[1],
"post_status": True,
"comment_tag": "",
'base_dir': ".",
}

return Config(cli_config)
Expand Down
15 changes: 6 additions & 9 deletions tests/test_parsers.py
Original file line number Diff line number Diff line change
Expand Up @@ -2,12 +2,8 @@
import os
import unittest

try:
from unittest.mock import patch
except ImportError:
from mock import patch

from lintly.parsers import PARSERS
from lintly.config import Config


class ParserTestCaseMixin(object):
Expand Down Expand Up @@ -57,6 +53,7 @@ def load_linter_output(file_name):
return linter_output.read()

def setUp(self):
Config.BASE_DIR = '.'
self.linter_output = self.load_linter_output(self.linter_output_file_name)

def test_parse_violations(self):
Expand Down Expand Up @@ -137,8 +134,8 @@ class ESLintParserTestCase(ParserTestCaseMixin, unittest.TestCase):
]
}

@patch('lintly.parsers.ESLintParser._get_working_dir', return_value='/Users/grant/project')
def test_parse_violations(self, _get_working_dir_mock):
def test_parse_violations(self):
Config.BASE_DIR = '/Users/grant/project'
super(ESLintParserTestCase, self).test_parse_violations()


Expand Down Expand Up @@ -168,8 +165,8 @@ class BlackParserTestCase(ParserTestCaseMixin, unittest.TestCase):
for file_path in ['lintly/violations.py', 'lintly/parsers.py']
}

@patch('lintly.parsers.BlackParser._get_working_dir', return_value='/Users/jouyuy/Dev/workspace/Lintly')
def test_parse_violations(self, _get_working_dir_mock):
def test_parse_violations(self):
Config.BASE_DIR = '/Users/jouyuy/Dev/workspace/Lintly'
super(BlackParserTestCase, self).test_parse_violations()


Expand Down