rename system and script languages to unsupported / unsupported_script

This commit is contained in:
anthony sottile 2025-11-08 13:13:18 -05:00
parent 3815e2e6d8
commit 725acc969a
9 changed files with 88 additions and 26 deletions

View file

@ -19,9 +19,9 @@ from pre_commit.languages import python
from pre_commit.languages import r from pre_commit.languages import r
from pre_commit.languages import ruby from pre_commit.languages import ruby
from pre_commit.languages import rust from pre_commit.languages import rust
from pre_commit.languages import script
from pre_commit.languages import swift 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] = { languages: dict[str, Language] = {
@ -43,8 +43,8 @@ languages: dict[str, Language] = {
'r': r, 'r': r,
'ruby': ruby, 'ruby': ruby,
'rust': rust, 'rust': rust,
'script': script,
'swift': swift, 'swift': swift,
'system': system, 'unsupported': unsupported,
'unsupported_script': unsupported_script,
} }
language_names = sorted(languages) language_names = sorted(languages)

View file

@ -6,6 +6,7 @@ import os.path
import re import re
import shlex import shlex
import sys import sys
from collections.abc import Callable
from collections.abc import Sequence from collections.abc import Sequence
from typing import Any from typing import Any
from typing import NamedTuple from typing import NamedTuple
@ -190,6 +191,42 @@ class DeprecatedDefaultStagesWarning(NamedTuple):
raise NotImplementedError 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( MANIFEST_HOOK_DICT = cfgv.Map(
'Hook', 'id', 'Hook', 'id',
@ -203,7 +240,7 @@ MANIFEST_HOOK_DICT = cfgv.Map(
cfgv.Required('id', cfgv.check_string), cfgv.Required('id', cfgv.check_string),
cfgv.Required('name', cfgv.check_string), cfgv.Required('name', cfgv.check_string),
cfgv.Required('entry', 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('alias', cfgv.check_string, ''),
cfgv.Optional('files', check_string_regex, ''), cfgv.Optional('files', check_string_regex, ''),
@ -368,8 +405,10 @@ META_HOOK_DICT = cfgv.Map(
'Hook', 'id', 'Hook', 'id',
cfgv.Required('id', cfgv.check_string), cfgv.Required('id', cfgv.check_string),
cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))), cfgv.Required('id', cfgv.check_one_of(tuple(k for k, _ in _meta))),
# language must be system # language must be `unsupported`
cfgv.Optional('language', cfgv.check_one_of({'system'}), 'system'), cfgv.Optional(
'language', cfgv.check_one_of({'unsupported'}), 'unsupported',
),
# entry cannot be overridden # entry cannot be overridden
NotAllowed('entry', cfgv.check_any), NotAllowed('entry', cfgv.check_any),
*( *(
@ -402,8 +441,10 @@ CONFIG_HOOK_DICT = cfgv.Map(
for item in MANIFEST_HOOK_DICT.items for item in MANIFEST_HOOK_DICT.items
if item.key != 'id' if item.key != 'id'
if item.key != 'stages' if item.key != 'stages'
if item.key != 'language' # remove
), ),
StagesMigrationNoDefault('stages', []), StagesMigrationNoDefault('stages', []),
LanguageMigration('language', cfgv.check_one_of(language_names)), # remove
*_COMMON_HOOK_WARNINGS, *_COMMON_HOOK_WARNINGS,
) )
LOCAL_HOOK_DICT = cfgv.Map( LOCAL_HOOK_DICT = cfgv.Map(

View file

@ -380,6 +380,26 @@ def test_no_warning_for_non_deprecated_default_stages(caplog):
assert caplog.record_tuples == [] 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( @pytest.mark.parametrize(
'manifest_obj', 'manifest_obj',
( (

View file

@ -1,9 +0,0 @@
from __future__ import annotations
from pre_commit.languages import system
from testing.language_helpers import run_language
def test_system_language(tmp_path):
expected = (0, b'hello hello world\n')
assert run_language(tmp_path, system, 'echo hello hello world') == expected

View file

@ -1,14 +1,14 @@
from __future__ import annotations 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 pre_commit.util import make_executable
from testing.language_helpers import run_language 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 = tmp_path.joinpath('main')
exe.write_text('#!/usr/bin/env bash\necho hello hello world\n') exe.write_text('#!/usr/bin/env bash\necho hello hello world\n')
make_executable(exe) make_executable(exe)
expected = (0, b'hello hello world\n') expected = (0, b'hello hello world\n')
assert run_language(tmp_path, script, 'main') == expected assert run_language(tmp_path, unsupported_script, 'main') == expected

View file

@ -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

View file

@ -17,7 +17,7 @@ from pre_commit.clientlib import CONFIG_SCHEMA
from pre_commit.clientlib import load_manifest from pre_commit.clientlib import load_manifest
from pre_commit.hook import Hook from pre_commit.hook import Hook
from pre_commit.languages import python 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.prefix import Prefix
from pre_commit.repository import _hook_installed from pre_commit.repository import _hook_installed
from pre_commit.repository import all_hooks from pre_commit.repository import all_hooks
@ -424,7 +424,7 @@ def test_manifest_hooks(tempdir_factory, store):
exclude_types=[], exclude_types=[],
files='', files='',
id='bash_hook', id='bash_hook',
language='script', language='unsupported_script',
language_version='default', language_version='default',
log_file='', log_file='',
minimum_pre_commit_version='0', minimum_pre_commit_version='0',
@ -457,7 +457,7 @@ def test_non_installable_hook_error_for_language_version(store, caplog):
'hooks': [{ 'hooks': [{
'id': 'system-hook', 'id': 'system-hook',
'name': 'system-hook', 'name': 'system-hook',
'language': 'system', 'language': 'unsupported',
'entry': 'python3 -c "import sys; print(sys.version)"', 'entry': 'python3 -c "import sys; print(sys.version)"',
'language_version': 'python3.10', 'language_version': 'python3.10',
}], }],
@ -469,7 +469,7 @@ def test_non_installable_hook_error_for_language_version(store, caplog):
msg, = caplog.messages msg, = caplog.messages
assert msg == ( assert msg == (
'The hook `system-hook` specifies `language_version` but is using ' '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?' 'Perhaps you meant to use a specific language?'
) )
@ -480,7 +480,7 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog):
'hooks': [{ 'hooks': [{
'id': 'system-hook', 'id': 'system-hook',
'name': 'system-hook', 'name': 'system-hook',
'language': 'system', 'language': 'unsupported',
'entry': 'python3 -c "import sys; print(sys.version)"', 'entry': 'python3 -c "import sys; print(sys.version)"',
'additional_dependencies': ['astpretty'], 'additional_dependencies': ['astpretty'],
}], }],
@ -492,14 +492,14 @@ def test_non_installable_hook_error_for_additional_dependencies(store, caplog):
msg, = caplog.messages msg, = caplog.messages
assert msg == ( assert msg == (
'The hook `system-hook` specifies `additional_dependencies` but is ' '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?' 'Perhaps you meant to use a specific language?'
) )
def test_args_with_spaces_and_quotes(tmp_path): def test_args_with_spaces_and_quotes(tmp_path):
ret = run_language( ret = run_language(
tmp_path, system, tmp_path, unsupported,
f"{shlex.quote(sys.executable)} -c 'import sys; print(sys.argv[1:])'", f"{shlex.quote(sys.executable)} -c 'import sys; print(sys.argv[1:])'",
('i have spaces', 'and"\'quotes', '$and !this'), ('i have spaces', 'and"\'quotes', '$and !this'),
) )