From cdfdc1766907f6dbe4f1991db58edca5138de9e5 Mon Sep 17 00:00:00 2001 From: Nikita Vasilev Date: Sat, 22 Nov 2025 12:22:16 +0400 Subject: [PATCH] ci: automate code formatting and linting with git hooks --- .github/workflows/ci.yml | 30 ---------- .github/workflows/lint.yml | 47 +++++++++++++++ .swiftformat | 2 +- .swiftlint.yml | 4 +- Makefile | 19 ------- Mintfile | 2 - README.md | 2 +- .../ValidationResult+Equatable.swift | 6 +- .../Classes/Rules/URLValidationRule.swift | 2 +- .../ValidatorUI/Classes/IUIValidatable.swift | 2 +- .../FormField/FormField/FormField.swift | 2 +- .../FormValidationViewModifier.swift | 6 +- .../ValidationViewModifier.swift | 6 +- .../Rules/CreditCardValidationRuleTests.swift | 8 +-- codecov.yml | 4 +- hooks/pre-commit | 38 ------------- mise.toml | 10 ++-- mise/tasks/install.sh | 57 +++++++++++++++++++ mise/tasks/lint | 13 +++++ 19 files changed, 144 insertions(+), 116 deletions(-) create mode 100644 .github/workflows/lint.yml delete mode 100644 Makefile delete mode 100644 Mintfile delete mode 100755 hooks/pre-commit create mode 100755 mise/tasks/install.sh create mode 100755 mise/tasks/lint diff --git a/.github/workflows/ci.yml b/.github/workflows/ci.yml index 075d322..5eaa13e 100644 --- a/.github/workflows/ci.yml +++ b/.github/workflows/ci.yml @@ -23,16 +23,6 @@ concurrency: cancel-in-progress: true jobs: - SwiftLint: - runs-on: ubuntu-latest - steps: - - uses: actions/checkout@v5 - - name: GitHub Action for SwiftLint - uses: norio-nomura/action-swiftlint@3.2.1 - with: - args: --strict - env: - DIFF_BASE: ${{ github.base_ref }} macOS: name: ${{ matrix.name }} runs-on: ${{ matrix.runsOn }} @@ -205,23 +195,3 @@ jobs: with: name: MergedResult path: test_output/final - - discover-typos: - name: Discover Typos - runs-on: macos-15 - env: - DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer - steps: - - uses: actions/checkout@v5 - - - name: Set up Python environment - run: | - python3 -m venv .venv - source .venv/bin/activate - pip install --upgrade pip - pip install codespell - - - name: Discover typos - run: | - source .venv/bin/activate - codespell --ignore-words-list="hart,inout,msdos,sur" --skip="./.build/*,./.git/*" \ No newline at end of file diff --git a/.github/workflows/lint.yml b/.github/workflows/lint.yml new file mode 100644 index 0000000..f93dba2 --- /dev/null +++ b/.github/workflows/lint.yml @@ -0,0 +1,47 @@ +name: lint + +on: + push: + branches: + - main + pull_request: + paths: + - "Sources/**" + - ".github/workflows/ci.yml" + - "Tests/**" + +concurrency: + group: lint-${{ github.head_ref }} + cancel-in-progress: true + +jobs: + lint: + name: lint + runs-on: macos-15 + steps: + - uses: actions/checkout@v5 + with: + fetch-depth: 0 + - uses: jdx/mise-action@v3 + - name: Run + run: mise run lint + + discover-typos: + name: discover-typos + runs-on: macos-15 + env: + DEVELOPER_DIR: /Applications/Xcode_16.4.app/Contents/Developer + steps: + - uses: actions/checkout@v5 + + - name: Set up Python environment + run: | + python3 -m venv .venv + source .venv/bin/activate + pip install --upgrade pip + pip install codespell + + - name: Discover typos + run: | + source .venv/bin/activate + codespell --ignore-words-list="hart,inout,msdos,sur" --skip="./.build/*,./.git/*" \ No newline at end of file diff --git a/.swiftformat b/.swiftformat index 05a65eb..c38608c 100644 --- a/.swiftformat +++ b/.swiftformat @@ -1,6 +1,6 @@ # Stream rules ---swiftversion 6.2 +--swiftversion 5.10 # Use 'swiftformat --options' to list all of the possible options diff --git a/.swiftlint.yml b/.swiftlint.yml index ac1438d..3e3c3ec 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,5 +1,4 @@ excluded: - - Tests - Package.swift - Package@swift-5.10.swift - Package@swift-6.0.swift @@ -12,9 +11,9 @@ disabled_rules: - trailing_comma - todo - opening_brace + - statement_position opt_in_rules: # some rules are only opt-in - - anyobject_protocol - array_init - closure_body_length - closure_end_indentation @@ -43,7 +42,6 @@ opt_in_rules: # some rules are only opt-in - ibinspectable_in_extension - identical_operands - implicit_return - - inert_defer - joined_default_parameter - last_where - legacy_multiple diff --git a/Makefile b/Makefile deleted file mode 100644 index 856d64b..0000000 --- a/Makefile +++ /dev/null @@ -1,19 +0,0 @@ -all: bootstrap - -bootstrap: hook - mint bootstrap - -hook: - ln -sf ../../hooks/pre-commit .git/hooks/pre-commit - chmod +x .git/hooks/pre-commit - -mint: - mint bootstrap - -lint: - mint run swiftlint - -fmt: - mint run swiftformat Sources Tests - -.PHONY: all bootstrap hook mint lint fmt \ No newline at end of file diff --git a/Mintfile b/Mintfile deleted file mode 100644 index 4ca9786..0000000 --- a/Mintfile +++ /dev/null @@ -1,2 +0,0 @@ -nicklockwood/SwiftFormat@0.58.6 -realm/SwiftLint@0.62.2 \ No newline at end of file diff --git a/README.md b/README.md index 68351f4..c4216bd 100644 --- a/README.md +++ b/README.md @@ -259,7 +259,7 @@ dependencies: [ Bootstrapping development environment ``` -make bootstrap +mise install ``` Please feel free to help out with this project! If you see something that could be made better or want a new feature, open up an issue or send a Pull Request! diff --git a/Sources/ValidatorCore/Classes/Extensions/ValidationResult+Equatable.swift b/Sources/ValidatorCore/Classes/Extensions/ValidationResult+Equatable.swift index a602167..78ebc7e 100644 --- a/Sources/ValidatorCore/Classes/Extensions/ValidationResult+Equatable.swift +++ b/Sources/ValidatorCore/Classes/Extensions/ValidationResult+Equatable.swift @@ -9,11 +9,11 @@ extension ValidationResult: Equatable { public static func == (lhs: ValidationResult, rhs: ValidationResult) -> Bool { switch (lhs, rhs) { case (.valid, .valid): - return true + true case let (.invalid(errors: lhs), .invalid(errors: rhs)): - return lhs.map(\.message).joined() == rhs.map(\.message).joined() + lhs.map(\.message).joined() == rhs.map(\.message).joined() default: - return false + false } } } diff --git a/Sources/ValidatorCore/Classes/Rules/URLValidationRule.swift b/Sources/ValidatorCore/Classes/Rules/URLValidationRule.swift index c52f6ae..184a7d9 100644 --- a/Sources/ValidatorCore/Classes/Rules/URLValidationRule.swift +++ b/Sources/ValidatorCore/Classes/Rules/URLValidationRule.swift @@ -1,6 +1,6 @@ // // Validator -// Copyright © 2025 Space Code. All rights reserved. +// Copyright © 2023 Space Code. All rights reserved. // import Foundation diff --git a/Sources/ValidatorUI/Classes/IUIValidatable.swift b/Sources/ValidatorUI/Classes/IUIValidatable.swift index 838d212..ff1fad9 100644 --- a/Sources/ValidatorUI/Classes/IUIValidatable.swift +++ b/Sources/ValidatorUI/Classes/IUIValidatable.swift @@ -21,7 +21,7 @@ public protocol IUIValidatable: AnyObject { /// - rule: The validation rule. /// /// - Returns: A validation result. - func validate(rule: some IValidationRule) -> ValidationResult where T == Input + func validate(rule: some IValidationRule) -> ValidationResult /// Validates an input value. /// diff --git a/Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormField/FormField.swift b/Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormField/FormField.swift index 07160e0..d7c273b 100644 --- a/Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormField/FormField.swift +++ b/Sources/ValidatorUI/Classes/SUI/Managers/FormField/FormField/FormField.swift @@ -15,8 +15,8 @@ public typealias ValidationPublisher = AnyPublisher public final class FormField: IFormField { // MARK: Properties - @Published /// The value to validate. + @Published private var value: Value /// The validation. diff --git a/Sources/ValidatorUI/Classes/SUI/ViewModifiers/FormValidationViewModifier.swift b/Sources/ValidatorUI/Classes/SUI/ViewModifiers/FormValidationViewModifier.swift index eb2a8e1..37939d9 100644 --- a/Sources/ValidatorUI/Classes/SUI/ViewModifiers/FormValidationViewModifier.swift +++ b/Sources/ValidatorUI/Classes/SUI/ViewModifiers/FormValidationViewModifier.swift @@ -35,7 +35,7 @@ public struct FormValidationViewModifier: ViewModifier { content validationMessageView }.onReceive(validationContainer.publisher) { result in - self.validationResult = result + validationResult = result } } @@ -44,9 +44,9 @@ public struct FormValidationViewModifier: ViewModifier { private var validationMessageView: some View { switch validationResult { case .valid: - return EmptyView().eraseToAnyView() + EmptyView().eraseToAnyView() case let .invalid(errors): - return content(errors).eraseToAnyView() + content(errors).eraseToAnyView() } } } diff --git a/Sources/ValidatorUI/Classes/SUI/ViewModifiers/ValidationViewModifier.swift b/Sources/ValidatorUI/Classes/SUI/ViewModifiers/ValidationViewModifier.swift index 26ce221..be20466 100644 --- a/Sources/ValidatorUI/Classes/SUI/ViewModifiers/ValidationViewModifier.swift +++ b/Sources/ValidatorUI/Classes/SUI/ViewModifiers/ValidationViewModifier.swift @@ -74,7 +74,7 @@ public struct ValidationViewModifier: ViewModifier { content .validation($item, rules: rules) { result in DispatchQueue.main.async { - self.validationResult = result + validationResult = result } } validationMessageView @@ -86,9 +86,9 @@ public struct ValidationViewModifier: ViewModifier { private var validationMessageView: some View { switch validationResult { case .valid: - return EmptyView().eraseToAnyView() + EmptyView().eraseToAnyView() case let .invalid(errors): - return content(errors).eraseToAnyView() + content(errors).eraseToAnyView() } } } diff --git a/Tests/ValidatorCoreTests/UnitTests/Rules/CreditCardValidationRuleTests.swift b/Tests/ValidatorCoreTests/UnitTests/Rules/CreditCardValidationRuleTests.swift index 9ac1993..fd7bd0d 100644 --- a/Tests/ValidatorCoreTests/UnitTests/Rules/CreditCardValidationRuleTests.swift +++ b/Tests/ValidatorCoreTests/UnitTests/Rules/CreditCardValidationRuleTests.swift @@ -40,10 +40,10 @@ final class CreditCardValidationRuleTests: XCTestCase { func test_validate_masterCardValid_shouldReturnTrue() { // given let rule = CreditCardValidationRule(types: [.masterCard], error: String.error) - let validMaster = "5500000000000004" + let validMasterCard = "5500000000000004" // when - let isValid = rule.validate(input: validMaster) + let isValid = rule.validate(input: validMasterCard) // then XCTAssertTrue(isValid) @@ -52,10 +52,10 @@ final class CreditCardValidationRuleTests: XCTestCase { func test_validate_masterCardInvalid_shouldReturnFalse() { // given let rule = CreditCardValidationRule(types: [.masterCard], error: String.error) - let invalidMaster = "550000000000" + let invalidMasterCard = "550000000000" // when - let isValid = rule.validate(input: invalidMaster) + let isValid = rule.validate(input: invalidMasterCard) // then XCTAssertFalse(isValid) diff --git a/codecov.yml b/codecov.yml index b415604..46d6f61 100644 --- a/codecov.yml +++ b/codecov.yml @@ -20,7 +20,7 @@ coverage: project: default: # The required coverage value - target: 50% + target: 75% # The leniency in hitting the target. Allow coverage to drop by X% threshold: 5% @@ -29,7 +29,7 @@ coverage: patch: default: # The required coverage value - target: 85% + target: 50% # Allow coverage to drop by X% threshold: 5% diff --git a/hooks/pre-commit b/hooks/pre-commit deleted file mode 100755 index 956fdcb..0000000 --- a/hooks/pre-commit +++ /dev/null @@ -1,38 +0,0 @@ -#!/bin/bash -git diff --diff-filter=d --staged --name-only | grep -e '\.swift$' | while read line; do - if [[ $line == *"/Generated"* ]]; then - echo "IGNORING GENERATED FILE: " "$line"; - else - mint run swiftformat swiftformat "${line}"; - git add "$line"; - fi -done - -LINT=$(which mint) -if [[ -e "${LINT}" ]]; then - # Export files in SCRIPT_INPUT_FILE_$count to lint against later - count=0 - while IFS= read -r file_path; do - export SCRIPT_INPUT_FILE_$count="$file_path" - count=$((count + 1)) - done < <(git diff --name-only --cached --diff-filter=d | grep ".swift$") - export SCRIPT_INPUT_FILE_COUNT=$count - - if [ "$count" -eq 0 ]; then - echo "No files to lint!" - exit 0 - fi - - echo "Found $count lintable files! Linting now.." - mint run swiftlint --use-script-input-files --strict --config .swiftlint.yml - RESULT=$? # swiftline exit value is number of errors - - if [ $RESULT -eq 0 ]; then - echo "🎉 Well done. No violation." - fi - exit $RESULT -else - echo "⚠️ WARNING: SwiftLint not found" - echo "⚠️ You might want to edit .git/hooks/pre-commit to locate your swiftlint" - exit 0 -fi \ No newline at end of file diff --git a/mise.toml b/mise.toml index 0cb51e8..2e45caf 100644 --- a/mise.toml +++ b/mise.toml @@ -1,8 +1,10 @@ [tools] git-cliff = "2.9.1" - -[hooks] -postinstall = "mise run install" +swiftlint = "0.62.2" +swiftformat = "0.58.6" [settings] -experimental = true \ No newline at end of file +experimental = true + +[hooks] +postinstall = "mise run install" \ No newline at end of file diff --git a/mise/tasks/install.sh b/mise/tasks/install.sh new file mode 100755 index 0000000..aeba03b --- /dev/null +++ b/mise/tasks/install.sh @@ -0,0 +1,57 @@ +#!/bin/bash + +set -e + +echo "🔧 Installing git hooks..." + +# Find git repository root +GIT_ROOT=$(git rev-parse --show-toplevel 2>/dev/null) + +if [ -z "$GIT_ROOT" ]; then + echo "❌ Error: Not a git repository" + exit 1 +fi + +echo "📁 Git root: $GIT_ROOT" + +# Create hooks directory if it doesn't exist +mkdir -p "$GIT_ROOT/.git/hooks" + +# Create pre-commit hook +cat > "$GIT_ROOT/.git/hooks/pre-commit" <<'HOOK_EOF' +#!/bin/bash + +echo "🔍 Running linters..." + +echo "📝 Formatting staged Swift files..." +git diff --diff-filter=d --staged --name-only | grep -e '\.swift$' | while read line; do + if [[ $line == *"/Generated"* ]]; then + echo "⏭️ Skipping generated file: $line" + else + echo "✨ Formatting: $line" + mise exec swiftformat -- swiftformat "${line}" + git add "$line" + fi +done + +if ! mise run lint; then + echo "❌ Lint failed. Please fix the issues before committing." + echo "💡 Tip: Run 'mise run format' to auto-fix some issues" + echo "⚠️ To skip this hook, use: git commit --no-verify" + exit 1 +fi + +echo "✅ All checks passed!" +exit 0 +HOOK_EOF + +chmod +x "$GIT_ROOT/.git/hooks/pre-commit" + +echo "✅ Git hooks installed successfully!" +echo "📍 Hook location: $GIT_ROOT/.git/hooks/pre-commit" +echo "" +echo "Pre-commit hook will:" +echo " 1. Format staged Swift files (except /Generated)" +echo " 2. Run mise lint" +echo "" +echo "To skip the hook, use: git commit --no-verify" \ No newline at end of file diff --git a/mise/tasks/lint b/mise/tasks/lint new file mode 100755 index 0000000..cd07c0c --- /dev/null +++ b/mise/tasks/lint @@ -0,0 +1,13 @@ +#!/bin/bash +#MISE description="Lint the validator package using SwiftLint and SwiftFormat" +#MISE usage flag "-f --fix" help="Fix the fixable issues" + +set -eo pipefail + +if [ "$usage_fix" = "true" ]; then + swiftformat Sources Tests + swiftlint lint --fix --strict --config .swiftlint.yml Sources Tests +else + swiftformat Sources Tests --lint + swiftlint lint --strict --config .swiftlint.yml Sources Tests +fi \ No newline at end of file