diff --git a/.clang-format b/.clang-format new file mode 100644 index 0000000..ddad26f --- /dev/null +++ b/.clang-format @@ -0,0 +1,88 @@ +# ESP32/Arduino C++ Code Formatting Configuration +# Based on Google style with embedded-specific modifications + +BasedOnStyle: Google + +# Indentation +IndentWidth: 2 +TabWidth: 2 +UseTab: Never +ContinuationIndentWidth: 4 + +# Braces +BreakBeforeBraces: Attach +Cpp11BracedListStyle: true + +# Line length - shorter for embedded readability +ColumnLimit: 100 + +# Spacing +SpaceAfterCStyleCast: false +SpaceAfterLogicalNot: false +SpaceAfterTemplateKeyword: true +SpaceBeforeAssignmentOperators: true +SpaceBeforeCpp11BracedList: false +SpaceBeforeCtorInitializerColon: true +SpaceBeforeInheritanceColon: true +SpaceBeforeParens: ControlStatements +SpaceBeforeRangeBasedForLoopColon: true +SpaceInEmptyParentheses: false +SpacesBeforeTrailingComments: 2 +SpacesInAngles: false +SpacesInContainerLiterals: true +SpacesInCStyleCastParentheses: false +SpacesInParentheses: false +SpacesInSquareBrackets: false + +# Alignment +AlignAfterOpenBracket: Align +AlignConsecutiveAssignments: false +AlignConsecutiveDeclarations: false +AlignEscapedNewlines: Left +AlignOperands: true +AlignTrailingComments: true + +# Breaking +AllowAllParametersOfDeclarationOnNextLine: true +AllowShortBlocksOnASingleLine: false +AllowShortCaseLabelsOnASingleLine: false +AllowShortFunctionsOnASingleLine: All +AllowShortIfStatementsOnASingleLine: true +AllowShortLoopsOnASingleLine: true +AlwaysBreakAfterDefinitionReturnType: None +AlwaysBreakAfterReturnType: None +AlwaysBreakBeforeMultilineStrings: true +AlwaysBreakTemplateDeclarations: true +BinPackArguments: true +BinPackParameters: true +BreakBeforeBinaryOperators: None +BreakBeforeTernaryOperators: true +BreakConstructorInitializersBeforeComma: false +BreakAfterJavaFieldAnnotations: false +BreakStringLiterals: true + +# Includes +IncludeBlocks: Preserve +SortIncludes: true + +# Pointers and references +DerivePointerAlignment: false +PointerAlignment: Left + +# Comments +ReflowComments: true + +# Preprocessor +IndentPPDirectives: None + +# Penalties for breaking (lower = more likely to break) +PenaltyBreakAssignment: 2 +PenaltyBreakBeforeFirstCallParameter: 1 +PenaltyBreakComment: 300 +PenaltyBreakFirstLessLess: 120 +PenaltyBreakString: 1000 +PenaltyExcessCharacter: 1000000 +PenaltyReturnTypeOnItsOwnLine: 200 + +# Language specific +Standard: Cpp11 \ No newline at end of file diff --git a/.editorconfig b/.editorconfig new file mode 100644 index 0000000..b89bbbd --- /dev/null +++ b/.editorconfig @@ -0,0 +1,36 @@ +# EditorConfig helps maintain consistent coding styles +# https://editorconfig.org/ + +root = true + +# All files +[*] +charset = utf-8 +end_of_line = lf +insert_final_newline = true +trim_trailing_whitespace = true + +# C/C++ source files +[*.{c,cpp,cc,cxx,h,hpp,hxx,ino}] +indent_style = space +indent_size = 2 +max_line_length = 100 + +# JSON files +[*.json] +indent_style = space +indent_size = 2 + +# Markdown files +[*.md] +trim_trailing_whitespace = false +max_line_length = off + +# YAML files +[*.{yml,yaml}] +indent_style = space +indent_size = 2 + +# Makefiles +[{Makefile,*.mk}] +indent_style = tab \ No newline at end of file diff --git a/.github/workflows/code-quality.yml b/.github/workflows/code-quality.yml new file mode 100644 index 0000000..1d7f4fb --- /dev/null +++ b/.github/workflows/code-quality.yml @@ -0,0 +1,92 @@ +name: Code Quality + +on: + push: + branches: [ main, develop ] + pull_request: + branches: [ main, develop ] + +jobs: + format-check: + runs-on: ubuntu-latest + name: Check Code Formatting + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install clang-format + run: | + sudo apt-get update + sudo apt-get install -y clang-format + + - name: Check formatting + run: | + find src/ -name "*.cpp" -o -name "*.h" -o -name "*.ino" | \ + xargs clang-format --dry-run --Werror --style=file + + format-fix: + runs-on: ubuntu-latest + name: Auto-fix Formatting + if: github.event_name == 'push' && github.ref == 'refs/heads/main' + + steps: + - name: Checkout code + uses: actions/checkout@v4 + with: + token: ${{ secrets.GITHUB_TOKEN }} + + - name: Install clang-format + run: | + sudo apt-get update + sudo apt-get install -y clang-format + + - name: Format code + run: | + find src/ -name "*.cpp" -o -name "*.h" -o -name "*.ino" | \ + xargs clang-format -i --style=file + + - name: Check for changes + id: verify-changed-files + run: | + if [ -n "$(git status --porcelain)" ]; then + echo "changed=true" >> $GITHUB_OUTPUT + else + echo "changed=false" >> $GITHUB_OUTPUT + fi + + - name: Commit formatted code + if: steps.verify-changed-files.outputs.changed == 'true' + run: | + git config --local user.email "action@github.com" + git config --local user.name "GitHub Action" + git add . + git commit -m "Auto-format code with clang-format [skip ci]" + git push + + lint: + runs-on: ubuntu-latest + name: Static Analysis + + steps: + - name: Checkout code + uses: actions/checkout@v4 + + - name: Install cppcheck + run: | + sudo apt-get update + sudo apt-get install -y cppcheck + + - name: Run cppcheck + run: | + cppcheck --enable=all --inconclusive --xml --xml-version=2 \ + --suppress=missingIncludeSystem \ + --suppress=unusedFunction \ + --suppress=unmatchedSuppression \ + src/ 2> cppcheck-report.xml || true + + - name: Upload cppcheck results + uses: actions/upload-artifact@v3 + with: + name: cppcheck-report + path: cppcheck-report.xml \ No newline at end of file diff --git a/.vscode/settings.json b/.vscode/settings.json new file mode 100644 index 0000000..3c66607 --- /dev/null +++ b/.vscode/settings.json @@ -0,0 +1,62 @@ +{ + // C/C++ formatting settings + "C_Cpp.clang_format_style": "file", + "C_Cpp.clang_format_fallbackStyle": "Google", + + // Editor settings + "editor.formatOnSave": true, + "editor.formatOnPaste": true, + "editor.formatOnType": false, + "editor.insertSpaces": true, + "editor.tabSize": 2, + "editor.detectIndentation": false, + "editor.trimAutoWhitespace": true, + "editor.rulers": [100], + + // File associations + "files.associations": { + "*.ino": "cpp", + "*.h": "cpp", + "*.cpp": "cpp" + }, + + // Auto-save + "files.autoSave": "onFocusChange", + + // Line endings + "files.eol": "\n", + + // Trailing whitespace + "files.trimTrailingWhitespace": true, + "files.insertFinalNewline": true, + + // Language-specific settings + "[cpp]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.formatOnSave": true + }, + "[c]": { + "editor.defaultFormatter": "ms-vscode.cpptools", + "editor.formatOnSave": true + }, + + // Extensions recommendations + "extensions.recommendations": [ + "ms-vscode.cpptools", + "ms-vscode.cpptools-extension-pack", + "platformio.platformio-ide" + ], + + // Git settings + "git.autofetch": true, + "git.enableSmartCommit": true, + + // Search exclusions + "search.exclude": { + "**/node_modules": true, + "**/.git": true, + "**/.DS_Store": true, + "**/build": true, + "**/.pio": true + } +} \ No newline at end of file diff --git a/scripts/format-code.sh b/scripts/format-code.sh new file mode 100755 index 0000000..32f0a30 --- /dev/null +++ b/scripts/format-code.sh @@ -0,0 +1,87 @@ +#!/bin/bash + +# ESP-APPs Code Formatting Script +# Formats all C/C++ source files using clang-format + +set -e + +# Colors for output +RED='\033[0;31m' +GREEN='\033[0;32m' +YELLOW='\033[1;33m' +NC='\033[0m' # No Color + +# Check if clang-format is installed +if ! command -v clang-format &> /dev/null; then + echo -e "${RED}Error: clang-format is not installed${NC}" + echo "Install it with:" + echo " Ubuntu/Debian: sudo apt-get install clang-format" + echo " macOS: brew install clang-format" + echo " Windows: Download from LLVM releases" + exit 1 +fi + +# Get script directory +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +PROJECT_ROOT="$(dirname "$SCRIPT_DIR")" + +echo -e "${YELLOW}ESP-APPs Code Formatter${NC}" +echo "Project root: $PROJECT_ROOT" + +# Check if .clang-format exists +if [ ! -f "$PROJECT_ROOT/.clang-format" ]; then + echo -e "${RED}Error: .clang-format file not found in project root${NC}" + exit 1 +fi + +# Find all C/C++ files +FILES=$(find "$PROJECT_ROOT/src" -name "*.cpp" -o -name "*.h" -o -name "*.ino" 2>/dev/null) + +if [ -z "$FILES" ]; then + echo -e "${YELLOW}No C/C++ files found to format${NC}" + exit 0 +fi + +echo "Found files to format:" +echo "$FILES" | sed 's/^/ /' + +# Check mode vs format mode +if [ "$1" = "--check" ]; then + echo -e "\n${YELLOW}Checking formatting...${NC}" + + ISSUES=0 + for file in $FILES; do + if ! clang-format --dry-run --Werror --style=file "$file" >/dev/null 2>&1; then + echo -e "${RED}✗${NC} $file" + ISSUES=$((ISSUES + 1)) + else + echo -e "${GREEN}✓${NC} $file" + fi + done + + if [ $ISSUES -gt 0 ]; then + echo -e "\n${RED}Found $ISSUES formatting issues${NC}" + echo "Run './scripts/format-code.sh' to fix them" + exit 1 + else + echo -e "\n${GREEN}All files are properly formatted${NC}" + fi +else + echo -e "\n${YELLOW}Formatting files...${NC}" + + for file in $FILES; do + echo "Formatting: $file" + clang-format -i --style=file "$file" + done + + echo -e "\n${GREEN}Formatting complete!${NC}" + + # Check if there are any changes + if command -v git &> /dev/null && git rev-parse --git-dir > /dev/null 2>&1; then + if [ -n "$(git status --porcelain)" ]; then + echo -e "${YELLOW}Files were modified. Review changes with 'git diff'${NC}" + else + echo -e "${GREEN}No changes needed - code was already formatted${NC}" + fi + fi +fi \ No newline at end of file diff --git a/test_formatTimeInfo.ino b/test_formatTimeInfo.ino deleted file mode 100644 index e0169e3..0000000 --- a/test_formatTimeInfo.ino +++ /dev/null @@ -1,175 +0,0 @@ -#include -#include - -// Mock the required structures and dependencies for testing -struct ESPAPP_TIME_INFO { - bool isValid; - bool isAM; - uint16_t year; - uint8_t month; - uint8_t day; - uint8_t hour; - uint8_t minute; - uint8_t second; -}; - -// Test class that exposes the private formatTimeInfo method for testing -class ESPAPP_Time_Test { -public: - void formatTimeInfo(ESPAPP_TIME_INFO *timeInfo, struct tm *timeDetails) { - timeInfo->year = timeDetails->tm_year + 1900; - timeInfo->month = timeDetails->tm_mon + 1; - timeInfo->day = timeDetails->tm_mday; - - // Handle hour in 12-hour format with AM/PM - if (timeDetails->tm_hour == 0) { - timeInfo->hour = 12; - timeInfo->isAM = true; - } - else if (timeDetails->tm_hour < 12) { - timeInfo->hour = timeDetails->tm_hour; - timeInfo->isAM = true; - } - else if (timeDetails->tm_hour == 12) { - timeInfo->hour = 12; - timeInfo->isAM = false; - } - else { - timeInfo->hour = timeDetails->tm_hour - 12; - timeInfo->isAM = false; - } - - timeInfo->minute = timeDetails->tm_min; - timeInfo->second = timeDetails->tm_sec; - timeInfo->isValid = true; - } -}; - -// Test helper functions -bool testPassed = true; -int testCount = 0; -int passedCount = 0; - -void assertEqual(int expected, int actual, const char* testName) { - testCount++; - if (expected == actual) { - passedCount++; - Serial.print("✓ "); - Serial.println(testName); - } else { - testPassed = false; - Serial.print("✗ "); - Serial.print(testName); - Serial.print(" - Expected: "); - Serial.print(expected); - Serial.print(", Got: "); - Serial.println(actual); - } -} - -void assertEqual(bool expected, bool actual, const char* testName) { - testCount++; - if (expected == actual) { - passedCount++; - Serial.print("✓ "); - Serial.println(testName); - } else { - testPassed = false; - Serial.print("✗ "); - Serial.print(testName); - Serial.print(" - Expected: "); - Serial.print(expected ? "true" : "false"); - Serial.print(", Got: "); - Serial.println(actual ? "true" : "false"); - } -} - -void test_formatTimeInfo_main_execution_path() { - Serial.println("\n=== Testing formatTimeInfo - Main Execution Path ==="); - - ESPAPP_Time_Test timeTest; - ESPAPP_TIME_INFO timeInfo; - struct tm timeDetails; - - // Test case 1: Midnight (00:00) -> 12:00 AM - timeDetails.tm_year = 123; // 2023 - timeDetails.tm_mon = 11; // December (0-based) - timeDetails.tm_mday = 25; // 25th - timeDetails.tm_hour = 0; // Midnight - timeDetails.tm_min = 30; - timeDetails.tm_sec = 45; - - timeTest.formatTimeInfo(&timeInfo, &timeDetails); - - assertEqual(2023, timeInfo.year, "Midnight: Year conversion"); - assertEqual(12, timeInfo.month, "Midnight: Month conversion (1-based)"); - assertEqual(25, timeInfo.day, "Midnight: Day"); - assertEqual(12, timeInfo.hour, "Midnight: Hour (12 AM)"); - assertEqual(true, timeInfo.isAM, "Midnight: AM flag"); - assertEqual(30, timeInfo.minute, "Midnight: Minutes"); - assertEqual(45, timeInfo.second, "Midnight: Seconds"); - assertEqual(true, timeInfo.isValid, "Midnight: Valid flag"); - - // Test case 2: Morning (09:15) -> 9:15 AM - timeDetails.tm_hour = 9; - timeDetails.tm_min = 15; - timeDetails.tm_sec = 0; - - timeTest.formatTimeInfo(&timeInfo, &timeDetails); - - assertEqual(9, timeInfo.hour, "Morning: Hour (9 AM)"); - assertEqual(true, timeInfo.isAM, "Morning: AM flag"); - assertEqual(15, timeInfo.minute, "Morning: Minutes"); - - // Test case 3: Noon (12:00) -> 12:00 PM - timeDetails.tm_hour = 12; - timeDetails.tm_min = 0; - timeDetails.tm_sec = 0; - - timeTest.formatTimeInfo(&timeInfo, &timeDetails); - - assertEqual(12, timeInfo.hour, "Noon: Hour (12 PM)"); - assertEqual(false, timeInfo.isAM, "Noon: PM flag"); - - // Test case 4: Afternoon (15:30) -> 3:30 PM - timeDetails.tm_hour = 15; - timeDetails.tm_min = 30; - timeDetails.tm_sec = 15; - - timeTest.formatTimeInfo(&timeInfo, &timeDetails); - - assertEqual(3, timeInfo.hour, "Afternoon: Hour (3 PM)"); - assertEqual(false, timeInfo.isAM, "Afternoon: PM flag"); - assertEqual(30, timeInfo.minute, "Afternoon: Minutes"); - assertEqual(15, timeInfo.second, "Afternoon: Seconds"); -} - -void setup() { - Serial.begin(115200); - while (!Serial) { - delay(10); - } - - Serial.println("ESPAPP_Time formatTimeInfo Unit Test"); - Serial.println("===================================="); - - test_formatTimeInfo_main_execution_path(); - - Serial.println("\n=== Test Results ==="); - Serial.print("Tests run: "); - Serial.println(testCount); - Serial.print("Tests passed: "); - Serial.println(passedCount); - Serial.print("Tests failed: "); - Serial.println(testCount - passedCount); - - if (testPassed) { - Serial.println("✓ ALL TESTS PASSED"); - } else { - Serial.println("✗ SOME TESTS FAILED"); - } -} - -void loop() { - // Empty loop - test runs once in setup() -} \ No newline at end of file