Skip to content
Merged
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
1 change: 0 additions & 1 deletion src/plugins/analysis/linter/apt-pkgs-runtime.txt
Original file line number Diff line number Diff line change
Expand Up @@ -2,4 +2,3 @@ liblua5.3-dev
lua5.3
luarocks
shellcheck
ruby
1 change: 0 additions & 1 deletion src/plugins/analysis/linter/dnf-pkgs-runtime.txt
Original file line number Diff line number Diff line change
Expand Up @@ -3,4 +3,3 @@ lua-devel
luarocks
nodejs
ShellCheck
ruby
6 changes: 3 additions & 3 deletions src/plugins/analysis/linter/install.py
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
#!/usr/bin/env python3
# pylint: disable=ungrouped-imports

import logging
from pathlib import Path
Expand All @@ -22,19 +23,18 @@ def install_other_packages(self):
run_cmd_with_logging('sudo luarocks install argparse')
run_cmd_with_logging('sudo luarocks install luacheck')
run_cmd_with_logging('sudo luarocks install luafilesystem')
run_cmd_with_logging('sudo gem install rubocop')

def install_docker_images(self):
run_cmd_with_logging('docker pull crazymax/linguist')
run_cmd_with_logging('docker pull cytopia/eslint')
run_cmd_with_logging('docker pull ghcr.io/phpstan/phpstan')
run_cmd_with_logging('docker pull pipelinecomponents/rubocop')


# Alias for generic use
Installer = LinterInstaller

if __name__ == '__main__':
logging.basicConfig(level=logging.INFO)
distribution = check_distribution()
installer = Installer(distribution)
installer = Installer(distribution=check_distribution())
installer.install()
90 changes: 52 additions & 38 deletions src/plugins/analysis/linter/internal/linters.py
Original file line number Diff line number Diff line change
@@ -1,9 +1,9 @@
import json
import logging
import shlex
import subprocess
from pathlib import Path
from subprocess import DEVNULL, PIPE, STDOUT
from subprocess import PIPE, STDOUT
from typing import List, Tuple

from docker.types import Mount

Expand Down Expand Up @@ -35,27 +35,28 @@ def run_eslint(file_path):

def run_shellcheck(file_path):
shellcheck_process = subprocess.run(
'shellcheck --format=json {}'.format(file_path),
f'shellcheck --format=json {file_path}',
shell=True,
stdout=PIPE,
stderr=STDOUT,
check=False,
universal_newlines=True,
)

if shellcheck_process.returncode == 2:
logging.debug('Failed to execute shellcheck:\n{}'.format(shellcheck_process.stdout))
return list()
logging.debug(f'Failed to execute shellcheck:\n{shellcheck_process.stdout}')
return []

try:
shellcheck_json = json.loads(shellcheck_process.stdout)
except json.JSONDecodeError:
return list()
return []

return _shellcheck_extract_relevant_warnings(shellcheck_json)
return _extract_shellcheck_warnings(shellcheck_json)


def _shellcheck_extract_relevant_warnings(shellcheck_json):
issues = list()
def _extract_shellcheck_warnings(shellcheck_json):
issues = []
for issue in shellcheck_json:
if issue['level'] in ['warning', 'error']:
issues.append({
Expand All @@ -72,10 +73,11 @@ def run_luacheck(file_path):
luacheckrc_path = Path(Path(__file__).parent, 'config', 'luacheckrc')

luacheck_process = subprocess.run(
'luacheck -q --ranges --config {} {}'.format(luacheckrc_path, file_path),
f'luacheck -q --ranges --config {luacheckrc_path} {file_path}',
shell=True,
stdout=PIPE,
stderr=STDOUT,
check=False,
universal_newlines=True,
)
return _luacheck_parse_linter_output(luacheck_process.stdout)
Expand All @@ -86,11 +88,11 @@ def _luacheck_parse_linter_output(output):
https://luacheck.readthedocs.io/en/stable/warnings.html
ignore_cases = ['(W611)', '(W612)', '(W613)', '(W614)', '(W621)', '(W631)']
'''
issues = list()
issues = []
for line in output.splitlines():
try:
line_number, columns, code_and_message = _luacheck_split_issue_line(line)
code, message = _luacheck_separate_message_and_code(code_and_message)
code, message = _separate_message_and_code(code_and_message)
if not code.startswith('(W6'):
issues.append({
'line': int(line_number),
Expand All @@ -100,8 +102,8 @@ def _luacheck_parse_linter_output(output):
})
else:
pass
except (IndexError, ValueError) as e:
logging.warning('Lualinter failed to parse line: {}\n{}'.format(line, e))
except (IndexError, ValueError) as error:
logging.warning(f'Lualinter failed to parse line: {line}\n{error}')

return issues

Expand All @@ -111,7 +113,7 @@ def _luacheck_split_issue_line(line):
return split_by_colon[1], split_by_colon[2], ':'.join(split_by_colon[3:]).strip()


def _luacheck_separate_message_and_code(message_string):
def _separate_message_and_code(message_string: str) -> Tuple[str, str]:
return message_string[1:5], message_string[6:].strip()


Expand All @@ -120,19 +122,26 @@ def _luacheck_get_first_column(columns):


def run_pylint(file_path):
pylint_process = subprocess.run('pylint --output-format=json {}'.format(file_path), shell=True, stdout=PIPE, stderr=STDOUT, universal_newlines=True)
pylint_process = subprocess.run(
f'pylint --output-format=json {file_path}',
shell=True,
stdout=PIPE,
stderr=STDOUT,
check=False,
universal_newlines=True
)

try:
pylint_json = json.loads(pylint_process.stdout)
except json.JSONDecodeError:
logging.warning('Failed to execute pylint:\n{}'.format(pylint_process.stdout))
return list()
logging.warning(f'Failed to execute pylint:\n{pylint_process.stdout}')
return []

return _pylint_extract_relevant_warnings(pylint_json)


def _pylint_extract_relevant_warnings(pylint_json):
issues = list()
issues = []
for issue in pylint_json:
if issue['type'] in ['error', 'warning']:
for unnecessary_information in ['module', 'obj', 'path', 'message-id']:
Expand All @@ -141,27 +150,32 @@ def _pylint_extract_relevant_warnings(pylint_json):
return issues


def run_rubocop(file_path):
rubocop_p = subprocess.run(
shlex.split(f'rubocop --format json {file_path}'),
stdout=PIPE,
stderr=DEVNULL,
check=False,
def run_rubocop(file_path: str) -> List[dict]:
container_path = '/input'
process = run_docker_container(
'pipelinecomponents/rubocop:latest',
combine_stderr_stdout=False,
mounts=[
Mount(container_path, file_path, type='bind', read_only=True),
],
command=f'rubocop --format json -- {container_path}',
)
linter_output = json.loads(rubocop_p.stdout)

issues = []
for offense in linter_output['files'][0]['offenses']:
issues.append(
{
'symbol': offense['cop_name'],
'line': offense['location']['start_line'],
'column': offense['location']['column'],
'message': offense['message'],
}
)

return issues
try:
linter_output = json.loads(process.stdout)
except json.JSONDecodeError:
logging.warning(f'Failed to execute rubocop linter:\n{process.stderr}')
return []

return [
{
'symbol': offense['cop_name'],
'line': offense['location']['start_line'],
'column': offense['location']['column'],
'message': offense['message'],
}
for offense in linter_output['files'][0]['offenses']
]


def run_phpstan(file_path):
Expand Down
5 changes: 4 additions & 1 deletion src/plugins/analysis/linter/test/test_ruby_linter.py
Original file line number Diff line number Diff line change
Expand Up @@ -44,7 +44,10 @@


def test_do_analysis(monkeypatch):
monkeypatch.setattr('plugins.analysis.linter.internal.linters.subprocess.run', lambda *_, **__: CompletedProcess('args', 0, stdout=MOCK_RESPONSE))
monkeypatch.setattr(
'plugins.analysis.linter.internal.linters.run_docker_container',
lambda *_, **__: CompletedProcess('args', 0, stdout=MOCK_RESPONSE)
)
result = run_rubocop('any/path')

assert len(result) == 1
Expand Down