diff --git a/src/plugins/cpp_plugin.py b/src/plugins/cpp_plugin.py index 98003ff..8307fd1 100644 --- a/src/plugins/cpp_plugin.py +++ b/src/plugins/cpp_plugin.py @@ -3,6 +3,7 @@ import re from collections import defaultdict import subprocess +import tempfile import yaml from typing import List, Tuple, Dict, Set, Any from base_interfaces import BasePreprocessor, BaseMinifier @@ -77,61 +78,64 @@ def preprocess(self, file_path: str, include_paths: List[str], defines: Dict[str source_content = include_regex.sub("", source_content) combined_content += source_content - temp_file_path: str = "temp_combined.cpp" - with open(temp_file_path, "w") as f: - f.write(combined_content) - - # First clang-format pass for initial formatting - _run_clang_format(['clang-format', '-i', temp_file_path]) - - with open(temp_file_path, "r") as f: - processed_content: str = f.read() - - # Inject tunable parameters (before removing comments, as we need // @tune markers) - if defines: - processed_content = self._inject_tunable_params(processed_content, defines) - - # Remove comments using regex - processed_content = re.sub(r'//.*\n', '\n', processed_content) # Single-line comments - processed_content = re.sub(r'/\*.*?\*/', '', processed_content, flags=re.DOTALL) # Multi-line comments - - # Check for prepkit_config.yaml for minification setting - config_file_path: str = os.path.join(os.getcwd(), "prepkit_config.yaml") - minify_output: bool = False - if os.path.exists(config_file_path): - try: - with open(config_file_path, 'r') as f: - config: Dict[str, Any] = yaml.safe_load(f) - minify_output = config.get("cpp_preprocess", {}).get("minify_output", False) - except yaml.YAMLError as e: - click.echo(f"Warning: Error reading prepkit_config.yaml: {e}. Using default settings.", err=True) - - if minify_output: - # Aggressive clang-format style for minification - minify_style: str = "{IndentWidth: 0, BreakBeforeBraces: Attach, SpaceAfterCStyleCast: false, SpacesInParentheses: false, CompactNamespaces: true, AllowShortBlocksOnASingleLine: Always, AllowShortFunctionsOnASingleLine: All}" - - # Final clang-format pass with minification style - with open(temp_file_path, "w") as f: - f.write(processed_content) - - _run_clang_format(['clang-format', '-i', '-style=' + minify_style, temp_file_path]) - - with open(temp_file_path, "r") as f: - minified_output: str = f.read() - minified_output = re.sub(r'\s+', '', minified_output) # Remove all whitespace - minified_output = re.sub(r'\n', '', minified_output) # Remove all newlines - final_output: str = minified_output - else: - # Final clang-format pass with default style - with open(temp_file_path, "w") as f: - f.write(processed_content) + with tempfile.NamedTemporaryFile(mode="w", suffix=".cpp", delete=False) as temp_file: + temp_file.write(combined_content) + temp_file_path: str = temp_file.name + try: + # First clang-format pass for initial formatting _run_clang_format(['clang-format', '-i', temp_file_path]) with open(temp_file_path, "r") as f: - final_output: str = f.read() + processed_content: str = f.read() + + # Inject tunable parameters (before removing comments, as we need // @tune markers) + if defines: + processed_content = self._inject_tunable_params(processed_content, defines) + + # Remove comments using regex + processed_content = re.sub(r'//.*\n', '\n', processed_content) # Single-line comments + processed_content = re.sub(r'/\*.*?\*/', '', processed_content, flags=re.DOTALL) # Multi-line comments + + # Check for prepkit_config.yaml for minification setting + config_file_path: str = os.path.join(os.getcwd(), "prepkit_config.yaml") + minify_output: bool = False + if os.path.exists(config_file_path): + try: + with open(config_file_path, 'r') as f: + config: Dict[str, Any] = yaml.safe_load(f) + minify_output = config.get("cpp_preprocess", {}).get("minify_output", False) + except yaml.YAMLError as e: + click.echo(f"Warning: Error reading prepkit_config.yaml: {e}. Using default settings.", err=True) + + if minify_output: + # Aggressive clang-format style for minification + minify_style: str = "{IndentWidth: 0, BreakBeforeBraces: Attach, SpaceAfterCStyleCast: false, SpacesInParentheses: false, CompactNamespaces: true, AllowShortBlocksOnASingleLine: Always, AllowShortFunctionsOnASingleLine: All}" + + # Final clang-format pass with minification style + with open(temp_file_path, "w") as f: + f.write(processed_content) + + _run_clang_format(['clang-format', '-i', '-style=' + minify_style, temp_file_path]) + + with open(temp_file_path, "r") as f: + minified_output: str = f.read() + minified_output = re.sub(r'\s+', '', minified_output) # Remove all whitespace + minified_output = re.sub(r'\n', '', minified_output) # Remove all newlines + final_output: str = minified_output + else: + # Final clang-format pass with default style + with open(temp_file_path, "w") as f: + f.write(processed_content) + + _run_clang_format(['clang-format', '-i', temp_file_path]) + + with open(temp_file_path, "r") as f: + final_output: str = f.read() + finally: + if os.path.exists(temp_file_path): + os.remove(temp_file_path) - os.remove(temp_file_path) return final_output else: # Report circular dependency error @@ -214,10 +218,9 @@ def minify(self, file_path: str) -> str: with open(file_path, 'r') as f: content = f.read() - # Create temporary file - temp_file_path = "temp_minify.cpp" - with open(temp_file_path, "w") as f: - f.write(content) + with tempfile.NamedTemporaryFile(mode="w", suffix=".cpp", delete=False) as temp_file: + temp_file.write(content) + temp_file_path = temp_file.name try: # Use clang-format with aggressive minification style diff --git a/src/plugins/rust_plugin.py b/src/plugins/rust_plugin.py index 6ca6e90..6777dcd 100644 --- a/src/plugins/rust_plugin.py +++ b/src/plugins/rust_plugin.py @@ -2,6 +2,7 @@ import os import re import subprocess +import tempfile from typing import List, Dict, Optional from base_interfaces import BasePreprocessor, BaseMinifier from preprocessing_utils import StringLiteralProtector, report_circular_dependency_error @@ -169,10 +170,11 @@ def _expand_mod_declarations( def _format_with_rustfmt(self, content: str) -> str: """Format Rust source with rustfmt if it is available, else return as-is.""" + temp_file_path: Optional[str] = None try: - temp_file_path: str = "temp_combined.rs" - with open(temp_file_path, "w") as f: - f.write(content) + with tempfile.NamedTemporaryFile(mode="w", suffix=".rs", delete=False) as temp_file: + temp_file.write(content) + temp_file_path = temp_file.name result = subprocess.run( ['rustfmt', temp_file_path], @@ -190,12 +192,14 @@ def _format_with_rustfmt(self, content: str) -> str: message = f"{message}\n{details}" click.echo(message, err=True) - os.remove(temp_file_path) except FileNotFoundError: # rustfmt not available, use unformatted output click.echo("Warning: rustfmt not found. Using unformatted output.", err=True) except subprocess.SubprocessError as e: click.echo(f"Warning: rustfmt failed: {e}. Using unformatted output.", err=True) + finally: + if temp_file_path and os.path.exists(temp_file_path): + os.remove(temp_file_path) return content diff --git a/tests/test_cpp_preprocessor.py b/tests/test_cpp_preprocessor.py index ba114c3..c797188 100644 --- a/tests/test_cpp_preprocessor.py +++ b/tests/test_cpp_preprocessor.py @@ -40,6 +40,14 @@ def test_cpp_preprocess_includes(cpp_preprocessor, temp_files): assert "#include \"header.hpp\"" not in output # Ensure local include is removed assert "#include \"constants.hpp\"" not in output # Ensure local include is removed +def test_cpp_preprocess_does_not_create_fixed_temp_file(cpp_preprocessor, temp_files, monkeypatch): + monkeypatch.chdir(temp_files) + + main_file = temp_files / "main.cpp" + cpp_preprocessor.preprocess(str(main_file), [str(temp_files)]) + + assert not (temp_files / "temp_combined.cpp").exists() + def test_cpp_preprocess_comments(cpp_preprocessor, temp_files): comments_file = temp_files / "comments_test.cpp" output = cpp_preprocessor.preprocess(str(comments_file), []) @@ -63,6 +71,14 @@ def test_cpp_minify(cpp_minifier, temp_files): assert "*/" not in output # Multi-line comments removed # Note: Some whitespace and newlines are preserved for compilation compatibility +def test_cpp_minify_does_not_create_fixed_temp_file(cpp_minifier, temp_files, monkeypatch): + monkeypatch.chdir(temp_files) + + minify_file = temp_files / "minify_test.cpp" + cpp_minifier.minify(str(minify_file)) + + assert not (temp_files / "temp_minify.cpp").exists() + def test_cpp_tunable_params_single(cpp_preprocessor, temp_files): """Test single tunable parameter injection.""" tune_file = temp_files / "tune_single.cpp" diff --git a/tests/test_error_messages.py b/tests/test_error_messages.py index 5b7b806..6d0422f 100644 --- a/tests/test_error_messages.py +++ b/tests/test_error_messages.py @@ -169,6 +169,7 @@ def test_missing_include_hint_message(self, tmp_path, capsys): def test_cpp_formatter_failure_is_reported(self, tmp_path, monkeypatch): """Test that clang-format failures produce a clear error.""" + monkeypatch.chdir(tmp_path) main_cpp = tmp_path / "main.cpp" main_cpp.write_text("int main(){return 0;}\n") @@ -184,6 +185,7 @@ def fake_run(*args, **kwargs): message = str(exc_info.value) assert "clang-format failed with exit code 1" in message assert "bad style" in message + assert not (tmp_path / "temp_combined.cpp").exists() def test_rustfmt_failure_warns_and_returns_unformatted_output(self, monkeypatch, capsys): """Test that optional rustfmt failures are visible but non-fatal.""" diff --git a/tests/test_rust_integration.py b/tests/test_rust_integration.py index eaa0001..655b83a 100644 --- a/tests/test_rust_integration.py +++ b/tests/test_rust_integration.py @@ -73,8 +73,9 @@ def test_empty_file(self, tmp_path): # Should not crash, just return empty content assert result == "\n" or result == "" - def test_single_file_no_modules(self, tmp_path): + def test_single_file_no_modules(self, tmp_path, monkeypatch): """Test preprocessing a single file with no mod declarations.""" + monkeypatch.chdir(tmp_path) main_rs = tmp_path / "main.rs" main_rs.write_text(""" fn main() { @@ -87,6 +88,7 @@ def test_single_file_no_modules(self, tmp_path): assert "fn main()" in result assert 'println!("Hello, World!")' in result + assert not (tmp_path / "temp_combined.rs").exists() def test_include_paths_resolution(self, tmp_path): """Test module resolution using include_paths parameter."""