diff --git a/.github/workflows/SyntaxKit.yml b/.github/workflows/SyntaxKit.yml index ff369e5..7de7a26 100644 --- a/.github/workflows/SyntaxKit.yml +++ b/.github/workflows/SyntaxKit.yml @@ -151,3 +151,13 @@ jobs: swift run mint install yonaskolb/mint - name: Lint run: ./Scripts/lint.sh + + docs: + name: Documentation Validation + if: "!contains(github.event.head_commit.message, 'ci skip')" + needs: [build-ubuntu, build-macos] + runs-on: ubuntu-latest + steps: + - uses: actions/checkout@v4 + - name: Validate Documentation + run: ./Scripts/validate-docs.sh diff --git a/Scripts/lint.sh b/Scripts/lint.sh index 2bba0f6..fea08ac 100755 --- a/Scripts/lint.sh +++ b/Scripts/lint.sh @@ -21,7 +21,7 @@ echo "LintMode: $LINT_MODE" # More portable way to get script directory if [ -z "$SRCROOT" ]; then - SCRIPT_DIR=$(dirname "$(readlink -f "$0")") + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PACKAGE_DIR="${SCRIPT_DIR}/.." else PACKAGE_DIR="${SRCROOT}" @@ -80,47 +80,6 @@ if [ -z "$CI" ]; then run_command $MINT_RUN periphery scan $PERIPHERY_OPTIONS --disable-update-check fi -# Documentation quality checks -if [ -z "$SKIP_DOCS" ]; then - echo -e "\nšŸ” Running comprehensive documentation quality checks..." - - # DocC generation with warnings as errors - echo "Generating DocC documentation (warnings as errors)..." - local docc_output=$(mktemp) - if ! swift package generate-documentation --warnings-as-errors 2>"$docc_output"; then - echo "āŒ DocC generation failed due to warnings or errors" - echo "šŸ” Error details:" - while IFS= read -r line; do - echo " $line" - done < "$docc_output" - echo "" - echo "šŸ’” Common fixes:" - echo " • Add missing documentation comments (///) to public APIs" - echo " • Fix broken symbol references in documentation" - echo " • Resolve conflicting or ambiguous documentation links" - echo " • Check for invalid markdown syntax in .docc files" - rm "$docc_output" - ERRORS=$((ERRORS + 1)) - else - echo "āœ… DocC generation successful" - rm "$docc_output" - fi - - # Full documentation validation suite - echo "Running documentation validation suite..." - if ! $PACKAGE_DIR/Scripts/validate-docs.sh; then - ERRORS=$((ERRORS + 1)) - echo "" - echo -e "šŸ’” \033[1;33mDocumentation Quality Help:\033[0m" - echo " Run individual checks for faster debugging:" - echo " • swift package generate-documentation # Check for DocC warnings" - echo " • ./Scripts/api-coverage.sh --threshold 90 # Check API coverage" - echo " • ./Scripts/validate-docs.sh # Full validation with detailed output" - echo "" - echo " Skip documentation checks temporarily:" - echo " • SKIP_DOCS=1 ./Scripts/lint.sh" - fi -fi popd diff --git "a/Scripts/\"?|.sh" b/Scripts/validate-docs.sh similarity index 72% rename from "Scripts/\"?|.sh" rename to Scripts/validate-docs.sh index a69d905..9dbbb33 100755 --- "a/Scripts/\"?|.sh" +++ b/Scripts/validate-docs.sh @@ -1,9 +1,40 @@ #!/bin/bash -set -e # Exit on any error +#set -e # Exit on any error ERRORS=0 WARNINGS=0 +SKIP_BUILD=true +SKIP_CODE_EXAMPLES=false + +# Parse command line arguments +while [[ $# -gt 0 ]]; do + case $1 in + --build) + SKIP_BUILD=false + shift + ;; + --skip-code-examples) + SKIP_CODE_EXAMPLES=true + shift + ;; + -h|--help) + echo "Usage: $0 [OPTIONS]" + echo "Options:" + echo " --build Force rebuild of SyntaxKit before validation" + echo " --skip-code-examples Skip validating Swift code examples" + echo " -h, --help Show this help message" + echo "" + echo "By default, the script will use existing builds to speed up validation." + echo "Use --build if you need to ensure a fresh build before validation." + exit 0 + ;; + *) + echo "Unknown option: $1" + exit 1 + ;; + esac +done # Colors for output RED='\033[0;31m' @@ -17,7 +48,7 @@ echo "════════════════════════ # More portable way to get script directory if [ -z "$SRCROOT" ]; then - SCRIPT_DIR=$(dirname "$(readlink -f "$0")") + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PACKAGE_DIR="${SCRIPT_DIR}/.." else PACKAGE_DIR="${SRCROOT}" @@ -30,18 +61,29 @@ validate_external_urls() { echo -e "\n${BLUE}🌐 Validating External URLs...${NC}" # Extract URLs from all markdown files - local urls_file=$(mktemp) + if [ -n "$RUNNER_TEMP" ]; then + local urls_file="$RUNNER_TEMP/urls_file.txt" + else + local urls_file=$(mktemp) + fi - # Extract URLs more precisely + # Extract URLs more precisely using find instead of bash globs for portability { - # Extract from markdown links [text](url) - grep -h -o '\](https\?://[^)]*)' Sources/SyntaxKit/Documentation.docc/**/*.md README.md CONTRIBUTING-DOCS.md 2>/dev/null | \ - sed 's/](\(https\?:\/\/[^)]*\)).*/\1/' - - # Extract standalone URLs (not in markdown links or Swift package syntax) - grep -h -o 'https\?://[^[:space:])]*' Sources/SyntaxKit/Documentation.docc/**/*.md README.md CONTRIBUTING-DOCS.md 2>/dev/null | \ - grep -v -E '(\.git"|from:|package\(|url:)' | \ - sed 's/[,;."`]*$//' + # Find all markdown files and extract URLs from them + find Sources/SyntaxKit/Documentation.docc -name "*.md" -type f 2>/dev/null | while read -r file; do + # Extract from markdown links [text](url) + grep -h -o '\](https\?://[^)]*)' "$file" 2>/dev/null | sed 's/](\(https\?:\/\/[^)]*\)).*/\1/' || true + # Extract standalone URLs (not in markdown links or Swift package syntax) + grep -h -o 'https\?://[^[:space:])]*' "$file" 2>/dev/null | grep -v -E '(\.git"|from:|package\(|url:)' | sed 's/[,;."`]*$//' || true + done + + # Also check root level files if they exist + for file in README.md CONTRIBUTING-DOCS.md; do + if [ -f "$file" ]; then + grep -h -o '\](https\?://[^)]*)' "$file" 2>/dev/null | sed 's/](\(https\?:\/\/[^)]*\)).*/\1/' || true + grep -h -o 'https\?://[^[:space:])]*' "$file" 2>/dev/null | grep -v -E '(\.git"|from:|package\(|url:)' | sed 's/[,;."`]*$//' || true + fi + done } | grep -E '^https?://' | sort -u > "$urls_file" || true if [ ! -s "$urls_file" ]; then @@ -88,7 +130,7 @@ validate_docc_links() { echo -e "\n${BLUE}šŸ“š Validating DocC Internal Links...${NC}" # Find all DocC link references like - local docc_links=$(grep -h -o ']*>' Sources/SyntaxKit/Documentation.docc/**/*.md 2>/dev/null | sort -u || true) + local docc_links=$(find Sources/SyntaxKit/Documentation.docc -name "*.md" -type f -exec grep -h -o ']*>' {} \; 2>/dev/null | sort -u || true) if [ -z "$docc_links" ]; then echo -e "${GREEN}āœ… No DocC links found to validate${NC}" @@ -130,7 +172,7 @@ validate_swift_symbols() { echo -e "\n${BLUE}šŸ”§ Validating Swift Symbol References...${NC}" # Find all double-backtick symbol references in SyntaxKit docs - local symbol_refs=$(grep -h -o '``[^`]*``' Sources/SyntaxKit/Documentation.docc/**/*.md 2>/dev/null | sort -u || true) + local symbol_refs=$(find Sources/SyntaxKit/Documentation.docc -name "*.md" -type f -exec grep -h -o '``[^`]*``' {} \; 2>/dev/null | sort -u || true) if [ -z "$symbol_refs" ]; then echo -e "${GREEN}āœ… No Swift symbol references found to validate${NC}" @@ -174,7 +216,7 @@ validate_cross_references() { echo -e "\n${BLUE}šŸ”— Validating Cross-References...${NC}" # Extract references to other tutorials and articles - local cross_refs=$(grep -h -o '\[.*\]([^)]*\.md)' Sources/SyntaxKit/Documentation.docc/**/*.md 2>/dev/null | sort -u || true) + local cross_refs=$(find Sources/SyntaxKit/Documentation.docc -name "*.md" -type f -exec grep -h -o '\[.*\]([^)]*\.md)' {} \; 2>/dev/null | sort -u || true) if [ -z "$cross_refs" ]; then echo -e "${GREEN}āœ… No cross-references found to validate${NC}" @@ -217,7 +259,7 @@ validate_api_coverage() { # More portable way to get script directory if [ -z "$SRCROOT" ]; then - SCRIPT_DIR=$(dirname "$(readlink -f "$0")") + SCRIPT_DIR="$(cd "$(dirname "$0")" && pwd)" PACKAGE_DIR="${SCRIPT_DIR}/.." else PACKAGE_DIR="${SRCROOT}" @@ -247,7 +289,12 @@ validate_code_examples() { echo -e "\n${BLUE}šŸ’» Validating Swift Code Examples...${NC}" # Create temporary directory for extracted code - local temp_dir=$(mktemp -d) + if [ -n "$RUNNER_TEMP" ]; then + local temp_dir="$RUNNER_TEMP/code_examples" + mkdir -p "$temp_dir" + else + local temp_dir=$(mktemp -d) + fi local examples_found=0 local examples_valid=0 local examples_failed=0 @@ -283,13 +330,25 @@ validate_code_examples() { ' "$file" } - # Build SyntaxKit first for type checking - echo -e "${BLUE}šŸ—ļø Building SyntaxKit for type checking...${NC}" - if ! swift build --quiet; then - echo -e "${RED}āŒ Failed to build SyntaxKit. Cannot validate code examples.${NC}" - rm -rf "$temp_dir" - ((ERRORS++)) - return 1 + # Build SyntaxKit first for type checking (only if not already built and not skipped) + if [ "$SKIP_BUILD" = false ]; then + if [ ! -d ".build" ] || [ ! -f ".build/debug/skit" ]; then + echo -e "${BLUE}šŸ—ļø Building SyntaxKit for type checking...${NC}" + if ! swift build --quiet; then + echo -e "${RED}āŒ Failed to build SyntaxKit. Cannot validate code examples.${NC}" + rm -rf "$temp_dir" + ((ERRORS++)) + return 1 + fi + else + echo -e "${BLUE}ā™»ļø Using existing SyntaxKit build for type checking...${NC}" + fi + else + echo -e "${BLUE}⚔ Skipping SyntaxKit build (default behavior for speed)${NC}" + if [ ! -d ".build" ]; then + echo -e "${YELLOW}āš ļø Warning: No .build directory found, use --build flag to build first${NC}" + ((WARNINGS++)) + fi fi # Process all documentation files @@ -304,14 +363,20 @@ validate_code_examples() { echo -n " Validating $(basename "$swift_file"): " - # Create a temporary Swift package to validate the code - local temp_package_dir=$(mktemp -d) - local package_swift="$temp_package_dir/Package.swift" - local main_swift="$temp_package_dir/Sources/TestExample/main.swift" - - # Create Package.swift with SyntaxKit dependency - cat > "$package_swift" << EOF -// swift-tools-version: 5.9 + # Create a temporary Swift package to validate the code (reuse if possible) + if [ -z "$temp_package_dir" ]; then + if [ -n "$RUNNER_TEMP" ]; then + temp_package_dir="$RUNNER_TEMP/temp_package_validation" + else + temp_package_dir=$(mktemp -d) + fi + local package_swift="$temp_package_dir/Package.swift" + local sources_dir="$temp_package_dir/Sources/TestExample" + + # Create Package.swift with SyntaxKit dependency (only once) + mkdir -p "$sources_dir" + cat > "$package_swift" << EOF +// swift-tools-version: 6.0 import PackageDescription let package = Package( @@ -328,9 +393,10 @@ let package = Package( ] ) EOF + fi - # Create directory structure and copy example code - mkdir -p "$(dirname "$main_swift")" + # Copy example code to main.swift + local main_swift="$sources_dir/main.swift" cp "$swift_file" "$main_swift" # Try to build the temporary package @@ -347,17 +413,19 @@ EOF ((ERRORS++)) fi - # Clean up temporary package - rm -rf "$temp_package_dir" + # Note: temp_package_dir is cleaned up after all examples fi done <<< "$swift_files" fi - done < <(find Sources/SyntaxKit/Documentation.docc -name "*.md" -type f; \ - find . -maxdepth 1 -name "README.md" -type f; \ + done < <(find Sources/SyntaxKit/Documentation.docc -name "*.md" -type f 2>/dev/null; \ + find . -maxdepth 1 -name "README.md" -type f 2>/dev/null; \ find Examples -name "README.md" -type f 2>/dev/null || true) # Clean up rm -rf "$temp_dir" + if [ -n "$temp_package_dir" ]; then + rm -rf "$temp_package_dir" + fi # Report results echo -e "\n${BLUE}šŸ“Š Code Examples Summary:${NC}" @@ -413,7 +481,9 @@ provide_error_recovery() { echo " → Follow DocC best practices for API documentation" echo "" echo -e "${BLUE}šŸ”§ Quick Commands:${NC}" - echo "• Rerun validation: ./Scripts/validate-docs.sh" + echo "• Fast validation (default): ./Scripts/validate-docs.sh" + echo "• Skip slow code validation: ./Scripts/validate-docs.sh --skip-code-examples" + echo "• Full validation with rebuild: ./Scripts/validate-docs.sh --build" echo "• Generate docs: swift package generate-documentation" echo "• Check API coverage: ./Scripts/api-coverage.sh --threshold 90" echo "• Format code: ./Scripts/lint.sh" @@ -426,7 +496,12 @@ main() { validate_swift_symbols validate_cross_references validate_api_coverage - validate_code_examples + + if [ "$SKIP_CODE_EXAMPLES" = false ]; then + validate_code_examples + else + echo -e "\n${BLUE}⚔ Skipping Swift code examples validation${NC}" + fi echo -e "\n${BLUE}šŸ“Š Validation Summary${NC}" echo "═══════════════════════════════════"