Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

fix: linter config file creation #349

Merged
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: 1 addition & 0 deletions secureli/container.py
Original file line number Diff line number Diff line change
Expand Up @@ -108,6 +108,7 @@ class Container(containers.DeclarativeContainer):
git_ignore=git_ignore_service,
language_config=language_config_service,
data_loader=read_resource,
echo=echo,
)

"""Analyzes a given repo to try to identify the most common language"""
Expand Down
12 changes: 6 additions & 6 deletions secureli/resources/files/configs/javascript.config.yaml
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
eslintrc:
extends: ["google"]
env:
es6: true
plugins:
- prettier
filename: ".eslintrc.yaml"
settings:
extends: ["google"]
env:
es6: true
plugins: ["prettier"]
10 changes: 5 additions & 5 deletions secureli/resources/files/configs/typescript.config.yaml
Original file line number Diff line number Diff line change
@@ -1,5 +1,5 @@
eslintrc:
extends: ['google', 'plugin:@typescript-eslint/recommended', 'prettier']
parser: '@typescript-eslint/parser'
plugins:
- '@typescript-eslint'
filename: ".eslintrc.yaml"
settings:
extends: ["google", "plugin:@typescript-eslint/recommended", "prettier"]
parser: "@typescript-eslint/parser"
plugins: ["@typescript-eslint"]
1 change: 0 additions & 1 deletion secureli/services/language_config.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,6 @@
from secureli.resources.slugify import slugify
from secureli.utilities.hash import hash_config
from secureli.utilities.patterns import combine_patterns
import secureli.repositories.secureli_config as SecureliConfig


class LanguageNotSupportedError(Exception):
Expand Down
61 changes: 30 additions & 31 deletions secureli/services/language_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -3,6 +3,7 @@

import pydantic
import yaml
from secureli.abstractions.echo import EchoAbstraction

import secureli.repositories.secureli_config as SecureliConfig
from secureli.abstractions.pre_commit import PreCommitAbstraction
Expand Down Expand Up @@ -68,9 +69,18 @@ class UnexpectedReposResult(pydantic.BaseModel):
unexpected_repos: Optional[list[str]] = []


class LinterConfigData(pydantic.BaseModel):
"""
Represents the structure of a linter config file
"""

filename: str
settings: Any


class LinterConfig(pydantic.BaseModel):
language: str
linter_data: list[Any]
linter_data: list[LinterConfigData]


class BuildConfigResult(pydantic.BaseModel):
Expand All @@ -95,11 +105,13 @@ def __init__(
language_config: LanguageConfigService,
git_ignore: GitIgnoreService,
data_loader: Callable[[str], str],
echo: EchoAbstraction,
):
self.git_ignore = git_ignore
self.pre_commit_hook = pre_commit_hook
self.language_config = language_config
self.data_loader = data_loader
self.echo = echo

def apply_support(
self, languages: list[str], language_config_result: BuildConfigResult
Expand Down Expand Up @@ -232,41 +244,28 @@ def _build_pre_commit_config(
linter_configs=linter_configs,
)

@staticmethod
def _write_pre_commit_configs(
self,
all_linter_configs: list[LinterConfig],
) -> LanguageLinterWriteResult:
) -> None:
"""
Install any config files for given language to support any pre-commit commands.
i.e. Javascript ESLint requires a .eslintrc file to sufficiently use plugins and allow
for further customization for repo's flavor of Javascript
:return: LanguageLinterWriteResult
:param all_linter_configs: the applicable linter configs to create config files for in the repo
"""

num_configs_success = 0
num_configs_non_success = 0
non_success_messages = list[str]()

# parse through languages for their linter config if any.
for language_linter_configs in all_linter_configs:
# parse though each config for the given language.
for config in language_linter_configs.linter_data:
try:
config_name = list(config.keys())[0]
# generate relative file name and path.
config_file_name = f"{slugify(language_linter_configs.language)}.{config_name}.yaml"
path_to_config_file = (
SecureliConfig.FOLDER_PATH / ".secureli/{config_file_name}"
)
with open(path_to_config_file, "w") as f:
f.write(yaml.dump(config[config_name]))
num_configs_success += 1
except Exception as e:
num_configs_non_success += 1
non_success_messages.append(f"Unable to install config: {e}")

return LanguageLinterWriteResult(
num_successful=num_configs_success,
num_non_success=num_configs_non_success,
non_success_messages=non_success_messages,
)
linter_config_data = [
(linter_data, config.language)
for config in all_linter_configs
for linter_data in config.linter_data
]

for config, language in linter_config_data:
try:
with open(Path(SecureliConfig.FOLDER_PATH / config.filename), "w") as f:
f.write(yaml.dump(config.settings))
except:
self.echo.warning(
f"Failed to write {config.filename} config file for {language}"
)
82 changes: 77 additions & 5 deletions tests/services/test_language_support.py
Original file line number Diff line number Diff line change
Expand Up @@ -7,7 +7,11 @@
from secureli.abstractions.pre_commit import (
InstallResult,
)
from secureli.services.language_support import BuildConfigResult, LanguageSupportService
from secureli.services.language_support import (
LanguageSupportService,
LinterConfig,
LinterConfigData,
)
from secureli.services.language_config import (
LanguageConfigService,
LanguagePreCommitResult,
Expand All @@ -32,6 +36,8 @@ def mock_open_config(mocker: MockerFixture):
"""
)
mocker.patch("builtins.open", mock_open)
mock_open.return_value.write = MagicMock()
return mock_open


@pytest.fixture()
Expand Down Expand Up @@ -77,6 +83,12 @@ def mock_git_ignore() -> MagicMock:
return mock_git_ignore


@pytest.fixture()
def mock_echo() -> MagicMock:
mock_echo = MagicMock()
return mock_echo


@pytest.fixture()
def mock_language_config_service() -> LanguageConfigService:
mock_language_config_service = MagicMock()
Expand All @@ -90,12 +102,14 @@ def language_support_service(
mock_git_ignore: MagicMock,
mock_language_config_service: MagicMock,
mock_data_loader: MagicMock,
mock_echo: MagicMock,
) -> LanguageSupportService:
return LanguageSupportService(
pre_commit_hook=mock_pre_commit_hook,
git_ignore=mock_git_ignore,
language_config=mock_language_config_service,
data_loader=mock_data_loader,
echo=mock_echo,
)


Expand Down Expand Up @@ -233,7 +247,7 @@ def mock_loader_side_effect(resource):
version="abc123",
linter_config=LoadLinterConfigsResult(
successful=True,
linter_data=[{"key": {"example"}}],
linter_data=[{"filename": "test.txt", "settings": {}}],
),
config_data="""
repos:
Expand Down Expand Up @@ -267,7 +281,7 @@ def test_that_language_support_throws_exception_when_language_config_file_cannot
version="abc123",
linter_config=LoadLinterConfigsResult(
successful=True,
linter_data=[{"key": {"example"}}],
linter_data=[{"filename": "test.txt", "settings": {}}],
),
config_data="""
repos:
Expand Down Expand Up @@ -301,7 +315,7 @@ def test_that_language_support_handles_invalid_language_config(
version="abc123",
linter_config=LoadLinterConfigsResult(
successful=True,
linter_data=[{"key": {"example"}}],
linter_data=[{"filename": "test.txt", "settings": {}}],
),
config_data="",
)
Expand All @@ -328,7 +342,7 @@ def test_that_language_support_handles_empty_repos_list(
version="abc123",
linter_config=LoadLinterConfigsResult(
successful=True,
linter_data=[{"key": {"example"}}],
linter_data=[{"filename": "test.txt", "settings": {}}],
),
config_data="""
repos:
Expand All @@ -345,3 +359,61 @@ def test_that_language_support_handles_empty_repos_list(
)

assert build_config_result.config_data["repos"] == []


def test_write_pre_commit_configs_writes_successfully(
language_support_service: LanguageSupportService,
mock_open: MagicMock,
mock_echo: MagicMock,
):
configs = [
LinterConfig(
language="RadLag",
linter_data=[LinterConfigData(filename="rad-lint.yml", settings={})],
),
LinterConfig(
language="CoolLang",
linter_data=[LinterConfigData(filename="cool-lint.yml", settings={})],
),
]
language_support_service._write_pre_commit_configs(configs)

assert mock_open.call_count == len(configs)
assert mock_open.return_value.write.call_count == len(configs)
mock_echo.warning.assert_not_called()


def test_write_pre_commit_configs_ignores_empty_linter_arr(
language_support_service: LanguageSupportService,
mock_open: MagicMock,
mock_echo: MagicMock,
):
language_support_service._write_pre_commit_configs([])

mock_open.assert_not_called()
mock_open.return_value.write.assert_not_called()
mock_echo.warning.assert_not_called()


def test_write_pre_commit_configs_handle_exceptions(
language_support_service: LanguageSupportService,
mock_open: MagicMock,
mock_echo: MagicMock,
):
mock_open.side_effect = Exception("error")
mock_language = "CoolLang"
mock_filename = "cool-lint-config.yml"
language_support_service._write_pre_commit_configs(
[
LinterConfig(
language=mock_language,
linter_data=[LinterConfigData(filename=mock_filename, settings={})],
),
]
)

mock_open.assert_called_once()
mock_open.return_value.write.assert_not_called()
mock_echo.warning.assert_called_once_with(
f"Failed to write {mock_filename} config file for {mock_language}"
)