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
8 changes: 4 additions & 4 deletions pre_commit/all_languages.py
Original file line number Diff line number Diff line change
Expand Up @@ -19,9 +19,9 @@
from pre_commit.languages import r
from pre_commit.languages import ruby
from pre_commit.languages import rust
from pre_commit.languages import script
from pre_commit.languages import swift
from pre_commit.languages import system
from pre_commit.languages import unsupported
from pre_commit.languages import unsupported_script


languages: dict[str, Language] = {
Expand All @@ -43,8 +43,8 @@
'r': r,
'ruby': ruby,
'rust': rust,
'script': script,
'swift': swift,
'system': system,
'unsupported': unsupported,
'unsupported_script': unsupported_script,
}
language_names = sorted(languages)
47 changes: 44 additions & 3 deletions pre_commit/clientlib.py
Original file line number Diff line number Diff line change
Expand Up @@ -6,6 +6,7 @@
import re
import shlex
import sys
from collections.abc import Callable
from collections.abc import Sequence
from typing import Any
from typing import NamedTuple
Expand Down Expand Up @@ -190,6 +191,42 @@ def remove_default(self, dct: dict[str, Any]) -> None:
raise NotImplementedError


def _translate_language(name: str) -> str:
return {
'system': 'unsupported',
'script': 'unsupported_script',
}.get(name, name)


class LanguageMigration(NamedTuple): # remove
key: str
check_fn: Callable[[object], None]

def check(self, dct: dict[str, Any]) -> None:
if self.key not in dct:
return

with cfgv.validate_context(f'At key: {self.key}'):
self.check_fn(_translate_language(dct[self.key]))

def apply_default(self, dct: dict[str, Any]) -> None:
if self.key not in dct:
return

dct[self.key] = _translate_language(dct[self.key])

def remove_default(self, dct: dict[str, Any]) -> None:
raise NotImplementedError


class LanguageMigrationRequired(LanguageMigration): # replace with Required
def check(self, dct: dict[str, Any]) -> None:
if self.key not in dct:
raise cfgv.ValidationError(f'Missing required key: {self.key}')

super().check(dct)


MANIFEST_HOOK_DICT = cfgv.Map(
'Hook', 'id',

Expand All @@ -203,7 +240,7 @@ def remove_default(self, dct: dict[str, Any]) -> None:
cfgv.Required('id', cfgv.check_string),
cfgv.Required('name', cfgv.check_string),
cfgv.Required('entry', cfgv.check_string),
cfgv.Required('language', cfgv.check_one_of(language_names)),
LanguageMigrationRequired('language', cfgv.check_one_of(language_names)),
cfgv.Optional('alias', cfgv.check_string, ''),

cfgv.Optional('files', check_string_regex, ''),
Expand Down Expand Up @@ -368,8 +405,10 @@ def check(self, dct: dict[str, Any]) -> None:
'Hook', 'id',
cfgv.Required('id', cfgv.check_string),
cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))),
# language must be system
cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'),
# language must be `unsupported`
cfgv.Optional(
'language', cfgv.check_one_of({'unsupported'}), 'unsupported',
),
# entry cannot be overridden
NotAllowed('entry', cfgv.check_any),
*(
Expand Down Expand Up @@ -402,8 +441,10 @@ def check(self, dct: dict[str, Any]) -> None:
for item in MANIFEST_HOOK_DICT.items
if item.key != 'id'
if item.key != 'stages'
if item.key != 'language' # remove
),
StagesMigrationNoDefault('stages', []),
LanguageMigration('language', cfgv.check_one_of(language_names)), # remove
*_COMMON_HOOK_WARNINGS,
)
LOCAL_HOOK_DICT = cfgv.Map(
Expand Down
File renamed without changes.
20 changes: 20 additions & 0 deletions tests/clientlib_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -380,6 +380,26 @@ def test_no_warning_for_non_deprecated_default_stages(caplog):
assert caplog.record_tuples == []


def test_unsupported_language_migration():
cfg = {'repos': [sample_local_config(), sample_local_config()]}
cfg['repos'][0]['hooks'][0]['language'] = 'system'
cfg['repos'][1]['hooks'][0]['language'] = 'script'

cfgv.validate(cfg, CONFIG_SCHEMA)
ret = cfgv.apply_defaults(cfg, CONFIG_SCHEMA)

assert ret['repos'][0]['hooks'][0]['language'] == 'unsupported'
assert ret['repos'][1]['hooks'][0]['language'] == 'unsupported_script'


def test_unsupported_language_migration_language_required():
cfg = {'repos': [sample_local_config()]}
del cfg['repos'][0]['hooks'][0]['language']

with pytest.raises(cfgv.ValidationError):
cfgv.validate(cfg, CONFIG_SCHEMA)


@pytest.mark.parametrize(
'manifest_obj',
(
Expand Down
9 changes: 0 additions & 9 deletions tests/languages/system_test.py

This file was deleted.

Original file line number Diff line number Diff line change
@@ -1,14 +1,14 @@
from __future__ import annotations

from pre_commit.languages import script
from pre_commit.languages import unsupported_script
from pre_commit.util import make_executable
from testing.language_helpers import run_language


def test_script_language(tmp_path):
def test_unsupported_script_language(tmp_path):
exe = tmp_path.joinpath('main')
exe.write_text('#!/usr/bin/env bash\necho hello hello world\n')
make_executable(exe)

expected = (0, b'hello hello world\n')
assert run_language(tmp_path, script, 'main') == expected
assert run_language(tmp_path, unsupported_script, 'main') == expected
10 changes: 10 additions & 0 deletions tests/languages/unsupported_test.py
Original file line number Diff line number Diff line change
@@ -0,0 +1,10 @@
from __future__ import annotations

from pre_commit.languages import unsupported
from testing.language_helpers import run_language


def test_unsupported_language(tmp_path):
expected = (0, b'hello hello world\n')
ret = run_language(tmp_path, unsupported, 'echo hello hello world')
assert ret == expected
14 changes: 7 additions & 7 deletions tests/repository_test.py
Original file line number Diff line number Diff line change
Expand Up @@ -17,7 +17,7 @@
from pre_commit.clientlib import load_manifest
from pre_commit.hook import Hook
from pre_commit.languages import python
from pre_commit.languages import system
from pre_commit.languages import unsupported
from pre_commit.prefix import Prefix
from pre_commit.repository import _hook_installed
from pre_commit.repository import all_hooks
Expand Down Expand Up @@ -424,7 +424,7 @@ def test_manifest_hooks(tempdir_factory, store):
exclude_types=[],
files='',
id='bash_hook',
language='script',
language='unsupported_script',
language_version='default',
log_file='',
minimum_pre_commit_version='0',
Expand Down Expand Up @@ -457,7 +457,7 @@ def test_non_installable_hook_error_for_language_version(store, caplog):
'hooks': [{
'id': 'system-hook',
'name': 'system-hook',
'language': 'system',
'language': 'unsupported',
'entry': 'python3 -c "import sys; print(sys.version)"',
'language_version': 'python3.10',
}],
Expand All @@ -469,7 +469,7 @@ def test_non_installable_hook_error_for_language_version(store, caplog):
msg, = caplog.messages
assert msg == (
'The hook `system-hook` specifies `language_version` but is using '
'language `system` which does not install an environment. '
'language `unsupported` which does not install an environment. '
'Perhaps you meant to use a specific language?'
)

Expand All @@ -480,7 +480,7 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog):
'hooks': [{
'id': 'system-hook',
'name': 'system-hook',
'language': 'system',
'language': 'unsupported',
'entry': 'python3 -c "import sys; print(sys.version)"',
'additional_dependencies': ['astpretty'],
}],
Expand All @@ -492,14 +492,14 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog):
msg, = caplog.messages
assert msg == (
'The hook `system-hook` specifies `additional_dependencies` but is '
'using language `system` which does not install an environment. '
'using language `unsupported` which does not install an environment. '
'Perhaps you meant to use a specific language?'
)


def test_args_with_spaces_and_quotes(tmp_path):
ret = run_language(
tmp_path, system,
tmp_path, unsupported,
f"{shlex.quote(sys.executable)} -c 'import sys; print(sys.argv[1:])'",
('i have spaces', 'and"\'quotes', '$and !this'),
)
Expand Down