Development

Setting Up Development Environment

Prerequisites

  • Python 3.9+

  • PowerShell Core 7.0+

  • Git

  • uv (recommended) or pip

Clone and Setup

# Clone the repository
git clone https://github.com/thetestlabs/py-psscriptanalyzer.git
cd py-psscriptanalyzer

# Install uv if not already installed
pip install uv

# Install dependencies
uv sync --group dev --group docs

# Install the package in editable mode
uv pip install -e .

# Install pre-commit hooks
uv run pre-commit install

Verify Installation

# Test the CLI
py-psscriptanalyzer --help

# Run tests
uv run pytest

# Check code quality
uv run ruff check .
uv run mypy src/py_psscriptanalyzer/

Project Structure

py-psscriptanalyzer/
├── src/
│   └── py_psscriptanalyzer/
│       ├── __init__.py          # Main exports
│       ├── __main__.py          # CLI entry point
│       ├── constants.py         # Configuration constants
│       ├── core.py              # Core functionality
│       ├── powershell.py        # PowerShell integration
│       └── scripts.py           # PowerShell script generation
├── tests/
│   ├── __init__.py
│   └── test_simple.py           # Test suite
├── docs/                        # Documentation
├── examples/                    # Example PowerShell files
├── .github/workflows/           # CI/CD pipelines
├── pyproject.toml              # Package configuration
└── README.md

Development Workflow

1. Create Feature Branch

git checkout -b feature/your-feature-name

2. Make Changes

Follow these guidelines:

  • Code Style: Use Ruff for formatting and linting

  • Type Hints: Add type hints to all functions

  • Documentation: Update docstrings and docs

  • Tests: Add tests for new functionality

3. Test Your Changes

# Run all tests
uv run pytest

# Run with coverage
uv run pytest --cov=src/py_psscriptanalyzer --cov-report=html

# Test specific functionality
uv run pytest tests/test_simple.py::TestFindPowershell

# Run linting
uv run ruff check .
uv run ruff format .
uv run mypy src/py_psscriptanalyzer/

# Test pre-commit hooks
uv run pre-commit run --all-files

4. Test CLI Integration

# Test CLI directly
py-psscriptanalyzer test-good.ps1
py-psscriptanalyzer --format test-good.ps1

# Test pre-commit integration
echo "Write-Host 'test'" > test.ps1
git add test.ps1
git commit -m "Test commit"  # Should trigger hooks

5. Update Documentation

If you’ve added new features or changed behavior:

# Build documentation locally
cd docs
uv run sphinx-build -b html . _build/html

# Or use make
make html

# View documentation
open _build/html/index.html  # macOS
xdg-open _build/html/index.html  # Linux

6. Submit Pull Request

git push origin feature/your-feature-name
# Then create PR on GitHub

Testing

Running Tests

# Run all tests
uv run pytest

# Run with verbose output
uv run pytest -v

# Run specific test file
uv run pytest tests/test_simple.py

# Run specific test method
uv run pytest tests/test_simple.py::TestFindPowershell::test_find_powershell_success_first_executable

# Run with coverage
uv run pytest --cov=src/py_psscriptanalyzer --cov-report=html

Writing Tests

Tests are located in the tests/ directory. Follow these patterns:

Unit Test Example

"""Test for new functionality."""

import pytest
from unittest.mock import Mock, patch

from py_psscriptanalyzer.core import your_new_function


class TestYourNewFunction:
    """Test your_new_function."""

    def test_success_case(self) -> None:
        """Test successful execution."""
        result = your_new_function("input")
        assert result == "expected_output"

    def test_error_case(self) -> None:
        """Test error handling."""
        with pytest.raises(ValueError):
            your_new_function("invalid_input")

    @patch("py_psscriptanalyzer.powershell.subprocess.run")
    def test_with_mock(self, mock_run: Mock) -> None:
        """Test with mocked subprocess."""
        mock_run.return_value.returncode = 0
        result = your_new_function("input")
        assert result is not None

Integration Tests

Integration tests should test the full workflow:

def test_full_analysis_workflow(tmp_path):
    """Test complete analysis workflow."""
    # Create test PowerShell file
    ps_file = tmp_path / "test.ps1"
    ps_file.write_text("Write-Host 'Hello World'")

    # Run analysis
    result = run_script_analyzer([str(ps_file)])

    # Verify results
    assert result != 0  # Should find Write-Host issue

Code Style and Quality

Code Formatting

We use Ruff for code formatting and linting:

# Format code
uv run ruff format .

# Check for issues
uv run ruff check .

# Fix auto-fixable issues
uv run ruff check . --fix

Type Checking

We use MyPy for static type checking:

# Run type checking
uv run mypy src/py_psscriptanalyzer/

# Check specific file
uv run mypy src/py_psscriptanalyzer/core.py

Pre-commit Hooks

Pre-commit hooks run automatically on commit:

# Install hooks (one-time setup)
uv run pre-commit install

# Run manually on all files
uv run pre-commit run --all-files

# Skip hooks for a commit (discouraged)
git commit --no-verify -m "Emergency fix"

Documentation

Building Documentation

cd docs

# Install docs dependencies
uv sync --group docs

# Build HTML documentation
uv run sphinx-build -b html . _build/html

# Or use make
make html

# Build and watch for changes (if available)
make livehtml

Writing Documentation

  • Use Markdown for most documentation (processed by MyST)

  • Use reStructuredText only when advanced Sphinx features are needed

  • Include code examples for all features

  • Add docstrings to all public functions

Docstring Style

Use Google-style docstrings:

def example_function(param1: str, param2: int = 0) -> bool:
    """Short description of the function.

    Longer description with more details about what the function does,
    how it works, and any important notes.

    Args:
        param1: Description of the first parameter.
        param2: Description of the second parameter. Defaults to 0.

    Returns:
        Description of what the function returns.

    Raises:
        ValueError: Description of when this exception is raised.

    Example:
        Basic usage example:

        >>> result = example_function("hello", 42)
        >>> print(result)
        True
    """
    return True

Release Process

Version Management

Versions are managed in pyproject.toml:

[project]
version = "1.0.0"

Creating a Release

  1. Update version in pyproject.toml

  2. Update CHANGELOG.md with new features and fixes

  3. Update documentation if needed

  4. Create and push tag:

    git tag v1.0.0
    git push origin v1.0.0
    
  5. GitHub Actions will automatically build and publish to PyPI

Publishing to PyPI

Publishing is automated via GitHub Actions when a version tag is pushed. Manual publishing:

# Build package
uv build

# Check package
uv run twine check dist/*

# Upload to test PyPI (optional)
uv run twine upload --repository testpypi dist/*

# Upload to PyPI
uv run twine upload dist/*

Contributing Guidelines

Pull Request Process

  1. Fork the repository

  2. Create feature branch from main

  3. Make changes following our coding standards

  4. Add tests for new functionality

  5. Update documentation if needed

  6. Ensure all checks pass (tests, linting, type checking)

  7. Submit pull request with clear description

Commit Messages

Use conventional commit format:

feat: add new feature
fix: resolve bug in parser
docs: update installation guide
test: add integration tests
refactor: improve error handling
chore: update dependencies

Code Review

All changes require code review:

  • Functionality: Does it work as expected?

  • Tests: Are there adequate tests?

  • Documentation: Is it well documented?

  • Style: Does it follow our coding standards?

  • Performance: Any performance implications?

Debugging

Common Development Issues

PowerShell Not Found

# Check PowerShell installation
which pwsh
pwsh --version

# Set custom PowerShell path
export PY_PSSCRIPTANALYZER_POWERSHELL="/usr/local/bin/pwsh"

PSScriptAnalyzer Module Issues

# Check module availability
pwsh -Command "Get-Module -ListAvailable PSScriptAnalyzer"

# Install manually
pwsh -Command "Install-Module -Name PSScriptAnalyzer -Force -Scope CurrentUser"

Test Failures

# Run tests with more verbose output
uv run pytest -v -s

# Run specific failing test
uv run pytest tests/test_simple.py::TestFindPowershell::test_find_powershell_not_found -v

# Run with debugger
uv run pytest --pdb tests/test_simple.py

Logging and Debugging

Enable debug logging in your code:

import logging

# Set up debug logging
logging.basicConfig(level=logging.DEBUG)
logger = logging.getLogger(__name__)

def your_function():
    logger.debug("Starting function execution")
    # Your code here
    logger.debug("Function completed successfully")