Skip to content

Commit

Permalink
fix: linter config file creation (#349)
Browse files Browse the repository at this point in the history
closes #344

Fixes issue where linter config files (i.e. .eslintrc.yaml) were not
being created and also had generic file names. Files will now be created
using the filename and config data contents specified in the specific
yaml resources.
  • Loading branch information
kevin-orlando authored Jan 5, 2024
1 parent f70429f commit a59fa0b
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 48 deletions.
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}"
)

0 comments on commit a59fa0b

Please sign in to comment.