This commit is contained in:
ali 2026-01-28 22:19:40 +02:00
parent dcac02b3f2
commit 31091350c9
No known key found for this signature in database
GPG key ID: 44F9B42770617B9B
13 changed files with 30 additions and 831 deletions

View file

@ -10,9 +10,6 @@ import re
from aiservice.common.llm_output_utils import truncate_pathological_output
# Matches ```python:filepath blocks, captures (filepath, content)
MARKDOWN_CODE_BLOCK_WITH_PATH_PATTERN = re.compile(r"```python:([^\n]+)\n(.*?)\n```", re.DOTALL)
# Matches both ```python and ```python:filepath blocks, captures content only
MARKDOWN_CODE_BLOCK_PATTERN = re.compile(r"```python(?::[^\n]*)?\n(.*?)```", re.DOTALL)

View file

@ -14,6 +14,7 @@ from functools import lru_cache
@lru_cache(maxsize=100)
def validate_javascript_syntax(code: str) -> tuple[bool, str | None]:
# TODO(claude): DON'T do this, use some pytohn lib for this instead of spawning a new subprocess
# Note: code can be a multi-file code (markdown with file paths)
return True, None
"""Validate JavaScript syntax using Node.js.

View file

@ -139,6 +139,7 @@ class CodeRepairContext:
def validate_module(self) -> None:
"""Validate the module syntax based on language."""
# Skip validation for non-Python languages for now
# TODO: have some way to validate the syntax of the code for other languages like js & ts
if self.data.language != "python":
return
for _code in split_markdown_code(self.data.modified_source_code).values():
@ -147,7 +148,3 @@ class CodeRepairContext:
CodeAndExplanation(cst_module, "")
except (ValueError, ValidationError, cst.ParserSyntaxError): # noqa: TRY203
raise
# Keep for backward compatibility
def validate_python_module(self) -> None:
self.validate_module()

View file

@ -1,146 +1,8 @@
"""Multi-language support registry.
"""Multi-language support module.
This module provides a registry for language implementations and factory
functions to retrieve language-specific functionality.
This package contains language-specific implementations for code optimization
and test generation.
Usage:
from languages import get_language, register_language
# Get a language implementation
lang = get_language("python")
validator = lang.get_validator()
is_valid, error = validator.validate_syntax(code)
# Register a new language (typically done in language module __init__)
@register_language("python")
class PythonLanguage:
...
Subpackages:
js_ts: JavaScript and TypeScript support
"""
from __future__ import annotations
from typing import TYPE_CHECKING
if TYPE_CHECKING:
from languages.base import LanguageSupport
# Registry mapping language identifiers to their implementation classes
_LANGUAGE_REGISTRY: dict[str, type[LanguageSupport]] = {}
# Aliases for language names (e.g., "js" -> "javascript")
_LANGUAGE_ALIASES: dict[str, str] = {
"js": "javascript",
"ts": "typescript",
}
# Flag to track whether languages have been loaded
_languages_loaded = False
class UnsupportedLanguageError(ValueError):
"""Raised when an unsupported language is requested."""
def __init__(self, language: str) -> None:
supported = ", ".join(sorted(_LANGUAGE_REGISTRY.keys()))
super().__init__(f"Unsupported language: {language}. Supported languages: {supported}")
self.language = language
def register_language(lang_id: str):
"""Decorator to register a language implementation.
Args:
lang_id: The language identifier (e.g., "python", "javascript").
Returns:
A decorator that registers the class in the language registry.
Example:
@register_language("python")
class PythonLanguage:
language = "python"
...
"""
def decorator(cls: type[LanguageSupport]) -> type[LanguageSupport]:
_LANGUAGE_REGISTRY[lang_id] = cls
return cls
return decorator
def _load_languages() -> None:
"""Load all language implementations.
This is called lazily to avoid import issues during module initialization.
"""
# Import Python language support
try:
import languages.python # noqa: F401
except ImportError:
pass
# Import JavaScript/TypeScript language support
try:
import languages.js_ts # noqa: F401
except ImportError:
pass
def _ensure_languages_loaded() -> None:
"""Ensure language implementations are loaded."""
global _languages_loaded
if not _languages_loaded:
_load_languages()
_languages_loaded = True
def get_language(language: str) -> LanguageSupport:
"""Get language support implementation for the given language.
Args:
language: The language identifier (e.g., "python", "javascript", "js").
Returns:
An instance of the language support class.
Raises:
UnsupportedLanguageError: If the language is not registered.
"""
_ensure_languages_loaded()
# Normalize language identifier using aliases
normalized = _LANGUAGE_ALIASES.get(language.lower(), language.lower())
if normalized not in _LANGUAGE_REGISTRY:
raise UnsupportedLanguageError(language)
return _LANGUAGE_REGISTRY[normalized]()
def get_supported_languages() -> list[str]:
"""Get a list of supported language identifiers.
Returns:
A sorted list of supported language names.
"""
_ensure_languages_loaded()
return sorted(_LANGUAGE_REGISTRY.keys())
def is_language_supported(language: str) -> bool:
"""Check if a language is supported.
Args:
language: The language identifier to check.
Returns:
True if the language is supported, False otherwise.
"""
_ensure_languages_loaded()
normalized = _LANGUAGE_ALIASES.get(language.lower(), language.lower())
return normalized in _LANGUAGE_REGISTRY

View file

@ -1,98 +0,0 @@
"""Base protocols and interfaces for multi-language support.
This module defines the contracts that language implementations must satisfy.
"""
from __future__ import annotations
from typing import TYPE_CHECKING, Protocol, runtime_checkable
if TYPE_CHECKING:
pass
@runtime_checkable
class CodeValidator(Protocol):
"""Protocol for code syntax validation."""
def validate_syntax(self, code: str) -> tuple[bool, str | None]:
"""Validate code syntax.
Args:
code: The source code to validate.
Returns:
A tuple of (is_valid, error_message).
- is_valid: True if the code is syntactically valid.
- error_message: None if valid, otherwise a description of the error.
"""
...
@runtime_checkable
class LanguageSupport(Protocol):
"""Protocol for language-specific implementations.
Each supported language should implement this protocol to provide
language-specific functionality like validation and code formatting.
"""
@property
def language(self) -> str:
"""The language identifier (e.g., 'python', 'javascript')."""
...
def get_validator(self) -> CodeValidator:
"""Get the code validator for this language."""
...
def is_multi_context(self, code: str) -> bool:
"""Check if code is in multi-file markdown format.
Multi-file format uses ```language:filepath blocks.
Args:
code: The source code to check.
Returns:
True if the code is in multi-file markdown format.
"""
...
def get_code_block_tag(self) -> str:
"""Get the markdown code block tag for this language.
Returns:
The language tag used in markdown code blocks (e.g., 'python', 'javascript').
"""
...
def split_markdown_code(self, markdown: str) -> dict[str, str]:
"""Split markdown into filepath -> code dict.
Parses markdown with ```language:filepath blocks and returns
a dictionary mapping file paths to their code content.
Args:
markdown: The markdown text containing code blocks.
Returns:
A dictionary mapping file paths to code content.
"""
...
def group_code(self, file_to_code: dict[str, str]) -> str:
"""Combine code files into markdown format.
Args:
file_to_code: A dictionary mapping file paths to code content.
Returns:
Markdown-formatted string with code blocks for each file.
"""
...

View file

@ -1,204 +1,17 @@
"""JavaScript/TypeScript language support for the aiservice.
"""JavaScript/TypeScript language support.
This module provides JS/TS-specific implementations for code validation,
multi-file context detection, and code formatting.
Note: The full implementation will be moved here from optimizer_javascript.py
and testgen_javascript.py in Phase 3.
This package contains JS/TS-specific implementations for:
- Code optimization (optimizer.py)
- Line profiler optimization (optimizer_lp.py)
- Test generation (testgen.py)
"""
from __future__ import annotations
from languages.js_ts.optimizer import optimize_javascript
from languages.js_ts.optimizer_lp import optimize_javascript_code_line_profiler
from languages.js_ts.testgen import testgen_javascript
from aiservice.common.markdown_utils import split_markdown_code as _split_markdown_code
from languages import register_language
class JavaScriptValidator:
"""Validator for JavaScript code syntax.
Note: Currently uses a permissive fallback validation.
Full validation will be implemented in a future PR.
"""
def validate_syntax(self, code: str) -> tuple[bool, str | None]:
"""Validate JavaScript syntax.
Args:
code: The JavaScript code to validate.
Returns:
A tuple of (is_valid, error_message).
Note:
Currently returns (True, None) as a permissive default.
The full validation logic from javascript_validator.py
will be integrated in Phase 3.
"""
# TODO: Integrate full validation from aiservice/validators/javascript_validator.py
# For now, use permissive validation to match current behavior
return True, None
class TypeScriptValidator:
"""Validator for TypeScript code syntax.
Note: Currently uses a permissive fallback validation.
Full validation will be implemented in a future PR.
"""
def validate_syntax(self, code: str) -> tuple[bool, str | None]:
"""Validate TypeScript syntax.
Args:
code: The TypeScript code to validate.
Returns:
A tuple of (is_valid, error_message).
Note:
Currently returns (True, None) as a permissive default.
The full validation logic will be integrated in Phase 3.
"""
# TODO: Integrate full validation from aiservice/validators/javascript_validator.py
return True, None
@register_language("javascript")
class JavaScriptLanguage:
"""JavaScript language support implementation."""
@property
def language(self) -> str:
"""The language identifier."""
return "javascript"
def get_validator(self) -> JavaScriptValidator:
"""Get the JavaScript code validator."""
return JavaScriptValidator()
def is_multi_context(self, code: str) -> bool:
"""Check if code is in multi-file markdown format.
Multi-file JavaScript code starts with ```javascript: or ```js:
followed by a filepath.
Args:
code: The source code to check.
Returns:
True if the code is in multi-file markdown format.
"""
stripped = code.strip()
return stripped.startswith("```javascript:") or stripped.startswith("```js:")
def get_code_block_tag(self) -> str:
"""Get the markdown code block tag for JavaScript.
Returns:
The string 'javascript' used in markdown code blocks.
"""
return "javascript"
def split_markdown_code(self, markdown: str) -> dict[str, str]:
"""Split markdown into filepath -> code dict.
Parses markdown with ```javascript:filepath or ```js:filepath blocks.
Args:
markdown: The markdown text containing code blocks.
Returns:
A dictionary mapping file paths to code content.
"""
return _split_markdown_code(markdown, language="javascript")
def group_code(self, file_to_code: dict[str, str]) -> str:
"""Combine code files into markdown format.
Args:
file_to_code: A dictionary mapping file paths to code content.
Returns:
Markdown-formatted string with ```javascript:filepath blocks.
"""
blocks = []
for file_path, code in file_to_code.items():
normalized = code if code.endswith("\n") else code + "\n"
blocks.append(f"```javascript:{file_path}\n{normalized}```")
return "\n".join(blocks)
@register_language("typescript")
class TypeScriptLanguage:
"""TypeScript language support implementation."""
@property
def language(self) -> str:
"""The language identifier."""
return "typescript"
def get_validator(self) -> TypeScriptValidator:
"""Get the TypeScript code validator."""
return TypeScriptValidator()
def is_multi_context(self, code: str) -> bool:
"""Check if code is in multi-file markdown format.
Multi-file TypeScript code starts with ```typescript: or ```ts:
followed by a filepath.
Args:
code: The source code to check.
Returns:
True if the code is in multi-file markdown format.
"""
stripped = code.strip()
return stripped.startswith("```typescript:") or stripped.startswith("```ts:")
def get_code_block_tag(self) -> str:
"""Get the markdown code block tag for TypeScript.
Returns:
The string 'typescript' used in markdown code blocks.
"""
return "typescript"
def split_markdown_code(self, markdown: str) -> dict[str, str]:
"""Split markdown into filepath -> code dict.
Parses markdown with ```typescript:filepath or ```ts:filepath blocks.
Args:
markdown: The markdown text containing code blocks.
Returns:
A dictionary mapping file paths to code content.
"""
return _split_markdown_code(markdown, language="typescript")
def group_code(self, file_to_code: dict[str, str]) -> str:
"""Combine code files into markdown format.
Args:
file_to_code: A dictionary mapping file paths to code content.
Returns:
Markdown-formatted string with ```typescript:filepath blocks.
"""
blocks = []
for file_path, code in file_to_code.items():
normalized = code if code.endswith("\n") else code + "\n"
blocks.append(f"```typescript:{file_path}\n{normalized}```")
return "\n".join(blocks)
__all__ = [
"optimize_javascript",
"optimize_javascript_code_line_profiler",
"testgen_javascript",
]

View file

@ -1,80 +0,0 @@
"""Python language support for the aiservice.
This module provides Python-specific implementations for code validation,
multi-file context detection, and code formatting.
"""
from __future__ import annotations
from aiservice.common.markdown_utils import split_markdown_code as _split_markdown_code
from languages import register_language
from languages.python.validator import PythonValidator
@register_language("python")
class PythonLanguage:
"""Python language support implementation."""
@property
def language(self) -> str:
"""The language identifier."""
return "python"
def get_validator(self) -> PythonValidator:
"""Get the Python code validator."""
return PythonValidator()
def is_multi_context(self, code: str) -> bool:
"""Check if code is in multi-file markdown format.
Multi-file Python code starts with ```python: followed by a filepath.
Args:
code: The source code to check.
Returns:
True if the code is in multi-file markdown format.
"""
return code.strip().startswith("```python:")
def get_code_block_tag(self) -> str:
"""Get the markdown code block tag for Python.
Returns:
The string 'python' used in markdown code blocks.
"""
return "python"
def split_markdown_code(self, markdown: str) -> dict[str, str]:
"""Split markdown into filepath -> code dict.
Parses markdown with ```python:filepath blocks and returns
a dictionary mapping file paths to their code content.
Args:
markdown: The markdown text containing code blocks.
Returns:
A dictionary mapping file paths to code content.
"""
return _split_markdown_code(markdown, language="python")
def group_code(self, file_to_code: dict[str, str]) -> str:
"""Combine code files into markdown format.
Args:
file_to_code: A dictionary mapping file paths to code content.
Returns:
Markdown-formatted string with ```python:filepath blocks.
"""
blocks = []
for file_path, code in file_to_code.items():
# Ensure code ends with newline before closing ```
normalized = code if code.endswith("\n") else code + "\n"
blocks.append(f"```python:{file_path}\n{normalized}```")
return "\n".join(blocks)

View file

@ -1,35 +0,0 @@
"""Python code syntax validation.
This module provides a validator that uses libcst for Python syntax validation.
"""
from __future__ import annotations
import libcst as cst
from aiservice.common.cst_utils import parse_module_to_cst
class PythonValidator:
"""Validator for Python code syntax using libcst."""
def validate_syntax(self, code: str) -> tuple[bool, str | None]:
"""Validate Python syntax using libcst.
Args:
code: The Python code to validate.
Returns:
A tuple of (is_valid, error_message).
- is_valid: True if the code is syntactically valid Python.
- error_message: None if valid, otherwise the parse error message.
"""
try:
parse_module_to_cst(code)
return True, None
except cst.ParserSyntaxError as e:
return False, str(e)
except Exception as e:
# Catch any other parsing errors
return False, f"Parse error: {e}"

View file

@ -53,7 +53,7 @@ class OptimizeErrorResponseSchema(Schema):
##########################################################################################
# BaseOptimizerContext #
# BaseOptimizerContext [PYTHON ONLY] #
##########################################################################################
class BaseOptimizerContext:
def __init__(self, base_system_prompt: str, base_user_prompt: str, source_code: str) -> None:

View file

@ -8,6 +8,7 @@ from pydantic import ValidationError
from aiservice.common.cst_utils import parse_module_to_cst
from aiservice.common.markdown_utils import wrap_code_in_markdown
from aiservice.validators.javascript_validator import validate_javascript_syntax
from optimizer.context_utils.context_helpers import (
group_code,
is_markdown_structure_changed,
@ -90,10 +91,9 @@ class BaseRefinerContext:
if stripped_code == self.data.optimized_source_code.strip():
return False
# For JavaScript/TypeScript, skip Python-specific syntax validation
if self.data.language in ("javascript", "typescript"):
# Basic validation: check it's not empty and has some code-like content
return len(stripped_code) > 10 and any(c in stripped_code for c in "{}();")
valid, _ = validate_javascript_syntax(stripped_code)
return bool(valid)
try:
parse_module_to_cst(new_refined_code)
@ -153,12 +153,10 @@ class SingleRefinerContext(BaseRefinerContext):
def validate_code_syntax(self, code: str) -> None:
"""Validate code syntax based on language."""
# For JavaScript/TypeScript, skip Python-specific validation
if self.data.language in ("javascript", "typescript"):
# Basic validation: non-empty code
if not code.strip():
msg = "Empty code"
raise ValueError(msg)
valid, _ = validate_javascript_syntax(code)
if not valid:
raise ValueError("Invalid JavaScript syntax")
return
# Python validation using libcst
@ -199,10 +197,9 @@ class MultiRefinerContext(BaseRefinerContext):
"""Validate code syntax based on language."""
# For JavaScript/TypeScript, skip Python-specific validation
if self.data.language in ("javascript", "typescript"):
# Basic validation: non-empty code
if not code.strip():
msg = "Empty code"
raise ValueError(msg)
valid, _ = validate_javascript_syntax(code)
if not valid:
raise ValueError("Invalid JavaScript syntax")
return
# Python validation using libcst

View file

@ -287,13 +287,8 @@ async def optimize(
request: AuthenticatedRequest, data: OptimizeSchema
) -> tuple[int, OptimizeResponseSchema | OptimizeErrorResponseSchema]:
# Route based on language
logging.warning(f"[OPTIMIZE DEBUG] Received request with language='{data.language}' (type: {type(data.language)})")
if data.language in ("javascript", "typescript"):
logging.warning("[OPTIMIZE DEBUG] Routing to optimize_javascript")
return await optimize_javascript(request, data)
# Default: Python optimization
logging.warning("[OPTIMIZE DEBUG] Routing to optimize_python")
return await optimize_python(request, data)

View file

@ -1 +0,0 @@
"""Tests for the languages module."""

View file

@ -1,249 +0,0 @@
"""Tests for the language registry and language implementations."""
import pytest
from languages import (
UnsupportedLanguageError,
get_language,
get_supported_languages,
is_language_supported,
)
from languages.base import CodeValidator, LanguageSupport
class TestLanguageRegistry:
"""Tests for the language registry."""
def test_get_supported_languages_returns_all_registered(self):
"""Test that all registered languages are returned."""
languages = get_supported_languages()
assert "python" in languages
assert "javascript" in languages
assert "typescript" in languages
def test_is_language_supported_for_registered_languages(self):
"""Test is_language_supported for registered languages."""
assert is_language_supported("python")
assert is_language_supported("javascript")
assert is_language_supported("typescript")
def test_is_language_supported_for_aliases(self):
"""Test is_language_supported for language aliases."""
assert is_language_supported("js")
assert is_language_supported("ts")
def test_is_language_supported_false_for_unknown(self):
"""Test is_language_supported returns False for unknown languages."""
assert not is_language_supported("ruby")
assert not is_language_supported("go")
def test_get_language_returns_instance(self):
"""Test get_language returns an instance of the language class."""
py = get_language("python")
assert py is not None
assert py.language == "python"
def test_get_language_with_alias(self):
"""Test get_language works with aliases."""
js = get_language("js")
assert js.language == "javascript"
ts = get_language("ts")
assert ts.language == "typescript"
def test_get_language_case_insensitive(self):
"""Test get_language is case insensitive."""
py1 = get_language("Python")
py2 = get_language("PYTHON")
py3 = get_language("python")
assert py1.language == py2.language == py3.language == "python"
def test_get_language_raises_for_unknown(self):
"""Test get_language raises UnsupportedLanguageError for unknown languages."""
with pytest.raises(UnsupportedLanguageError) as exc_info:
get_language("ruby")
assert "ruby" in str(exc_info.value)
assert "Unsupported language" in str(exc_info.value)
class TestPythonLanguage:
"""Tests for the Python language implementation."""
@pytest.fixture
def python_lang(self):
"""Get Python language instance."""
return get_language("python")
def test_language_property(self, python_lang):
"""Test language property returns 'python'."""
assert python_lang.language == "python"
def test_get_code_block_tag(self, python_lang):
"""Test get_code_block_tag returns 'python'."""
assert python_lang.get_code_block_tag() == "python"
def test_get_validator_returns_validator(self, python_lang):
"""Test get_validator returns a CodeValidator."""
validator = python_lang.get_validator()
assert isinstance(validator, CodeValidator)
def test_validator_valid_python(self, python_lang):
"""Test validator accepts valid Python code."""
validator = python_lang.get_validator()
is_valid, error = validator.validate_syntax("def foo():\n return 42")
assert is_valid is True
assert error is None
def test_validator_invalid_python(self, python_lang):
"""Test validator rejects invalid Python code."""
validator = python_lang.get_validator()
is_valid, error = validator.validate_syntax("def foo( return 42")
assert is_valid is False
assert error is not None
def test_is_multi_context_false_for_plain_code(self, python_lang):
"""Test is_multi_context returns False for plain Python code."""
assert not python_lang.is_multi_context("def foo(): pass")
assert not python_lang.is_multi_context("```python\ndef foo(): pass\n```")
def test_is_multi_context_true_for_markdown_with_path(self, python_lang):
"""Test is_multi_context returns True for markdown with filepath."""
code = "```python:test.py\ndef foo(): pass\n```"
assert python_lang.is_multi_context(code)
def test_split_markdown_code(self, python_lang):
"""Test split_markdown_code parses markdown correctly."""
markdown = """```python:file1.py
def foo():
pass
```
```python:file2.py
def bar():
pass
```"""
result = python_lang.split_markdown_code(markdown)
assert "file1.py" in result
assert "file2.py" in result
assert "def foo():" in result["file1.py"]
assert "def bar():" in result["file2.py"]
def test_group_code(self, python_lang):
"""Test group_code formats code correctly."""
file_to_code = {
"file1.py": "def foo():\n pass",
"file2.py": "def bar():\n pass",
}
result = python_lang.group_code(file_to_code)
assert "```python:file1.py" in result
assert "```python:file2.py" in result
assert "def foo():" in result
assert "def bar():" in result
class TestJavaScriptLanguage:
"""Tests for the JavaScript language implementation."""
@pytest.fixture
def js_lang(self):
"""Get JavaScript language instance."""
return get_language("javascript")
def test_language_property(self, js_lang):
"""Test language property returns 'javascript'."""
assert js_lang.language == "javascript"
def test_get_code_block_tag(self, js_lang):
"""Test get_code_block_tag returns 'javascript'."""
assert js_lang.get_code_block_tag() == "javascript"
def test_get_validator_returns_validator(self, js_lang):
"""Test get_validator returns a CodeValidator."""
validator = js_lang.get_validator()
assert isinstance(validator, CodeValidator)
def test_is_multi_context_false_for_plain_code(self, js_lang):
"""Test is_multi_context returns False for plain JavaScript code."""
assert not js_lang.is_multi_context("function foo() {}")
assert not js_lang.is_multi_context("```javascript\nfunction foo() {}\n```")
def test_is_multi_context_true_for_markdown_with_path(self, js_lang):
"""Test is_multi_context returns True for markdown with filepath."""
code = "```javascript:test.js\nfunction foo() {}\n```"
assert js_lang.is_multi_context(code)
code_short = "```js:test.js\nfunction foo() {}\n```"
assert js_lang.is_multi_context(code_short)
def test_group_code(self, js_lang):
"""Test group_code formats code correctly."""
file_to_code = {
"file1.js": "function foo() {}",
"file2.js": "function bar() {}",
}
result = js_lang.group_code(file_to_code)
assert "```javascript:file1.js" in result
assert "```javascript:file2.js" in result
class TestTypeScriptLanguage:
"""Tests for the TypeScript language implementation."""
@pytest.fixture
def ts_lang(self):
"""Get TypeScript language instance."""
return get_language("typescript")
def test_language_property(self, ts_lang):
"""Test language property returns 'typescript'."""
assert ts_lang.language == "typescript"
def test_get_code_block_tag(self, ts_lang):
"""Test get_code_block_tag returns 'typescript'."""
assert ts_lang.get_code_block_tag() == "typescript"
def test_is_multi_context_true_for_markdown_with_path(self, ts_lang):
"""Test is_multi_context returns True for markdown with filepath."""
code = "```typescript:test.ts\nfunction foo(): void {}\n```"
assert ts_lang.is_multi_context(code)
code_short = "```ts:test.ts\nfunction foo(): void {}\n```"
assert ts_lang.is_multi_context(code_short)
def test_group_code(self, ts_lang):
"""Test group_code formats code correctly."""
file_to_code = {"file1.ts": "function foo(): void {}"}
result = ts_lang.group_code(file_to_code)
assert "```typescript:file1.ts" in result
class TestLanguageProtocolCompliance:
"""Tests to verify language implementations satisfy the protocols."""
@pytest.mark.parametrize("lang_id", ["python", "javascript", "typescript"])
def test_language_satisfies_protocol(self, lang_id):
"""Test that each language satisfies the LanguageSupport protocol."""
lang = get_language(lang_id)
assert isinstance(lang, LanguageSupport)
# Verify all protocol methods exist and return expected types
assert isinstance(lang.language, str)
assert isinstance(lang.get_code_block_tag(), str)
assert isinstance(lang.get_validator(), CodeValidator)
assert isinstance(lang.is_multi_context("test"), bool)
assert isinstance(lang.split_markdown_code("test"), dict)
assert isinstance(lang.group_code({"a.py": "code"}), str)
@pytest.mark.parametrize("lang_id", ["python", "javascript", "typescript"])
def test_validator_satisfies_protocol(self, lang_id):
"""Test that each validator satisfies the CodeValidator protocol."""
lang = get_language(lang_id)
validator = lang.get_validator()
assert isinstance(validator, CodeValidator)
# Verify validate_syntax returns expected tuple
result = validator.validate_syntax("test code")
assert isinstance(result, tuple)
assert len(result) == 2
assert isinstance(result[0], bool)
assert result[1] is None or isinstance(result[1], str)