From b1577d8d07882fb9d206171003c41b14623c2644 Mon Sep 17 00:00:00 2001 From: J Logan Date: Fri, 16 Jan 2026 15:50:47 -0800 Subject: [PATCH] Adds opt-in pre-commit hook for format and header checks. (#1062) - Closes #639. - Adds swift format configuration that removes lint checks so we can use `swift lint` to perform format-only tests. - Adds `check` target that invokes format and header checks. - Adds pre-commit script that runs `make check`. - Adds `pre-commit` target that installs the check script as a pre-commit hook. ## Type of Change - [ ] Bug fix - [x] New feature - [ ] Breaking change - [x] Documentation update ## Motivation and Context Avoids wasting time and commit rewrites. ## Testing - [x] Tested locally - [ ] Added/updated tests - [x] Added/updated docs --- .swift-format-nolint | 68 +++++++++++++++++++ BUILDING.md | 4 ++ Makefile | 17 +++++ .../Subcommands/Run/TestCLIRunCommand.swift | 10 +++ scripts/pre-commit.fmt | 16 +++++ 5 files changed, 115 insertions(+) create mode 100644 .swift-format-nolint create mode 100755 scripts/pre-commit.fmt diff --git a/.swift-format-nolint b/.swift-format-nolint new file mode 100644 index 00000000..8f600e50 --- /dev/null +++ b/.swift-format-nolint @@ -0,0 +1,68 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 4 + }, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : false, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 180, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLowerCamelCase" : false, + "AmbiguousTrailingClosureOverload" : false, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : false, + "FileScopedDeclarationPrivacy" : false, + "FullyIndirectEnum" : false, + "GroupNumericLiterals" : false, + "IdentifiersMustBeASCII" : false, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : false, + "NoAssignmentInExpressions" : false, + "NoBlockComments" : false, + "NoCasesWithOnlyFallthrough" : false, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : false, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : false, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : false, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : false, + "ReturnVoidInsteadOfEmptyTuple" : false, + "TypeNamesShouldBeCapitalized" : false, + "UseEarlyExits" : false, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : false, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 2, + "version" : 1 +} diff --git a/BUILDING.md b/BUILDING.md index 494b9f1a..6f7da793 100644 --- a/BUILDING.md +++ b/BUILDING.md @@ -118,3 +118,7 @@ To revert to using the Containerization dependency from your `Package.swift`: bin/container system stop bin/container system start ``` + +## Pre-commit hook + +Run `make pre-commit` to install a pre-commit hook that ensures that your changes have correct formatting and license headers when you run `git commit`. diff --git a/Makefile b/Makefile index 080aff46..241f9a6a 100644 --- a/Makefile +++ b/Makefile @@ -186,6 +186,7 @@ integration: init-block $(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLICreateCommand || exit_code=1 ; \ $(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand1 || exit_code=1 ; \ $(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand2 || exit_code=1 ; \ + $(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunCommand3 || exit_code=1 ; \ $(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIStatsCommand || exit_code=1 ; \ $(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIImagesCommand || exit_code=1 ; \ $(SWIFT) test -c $(BUILD_CONFIGURATION) $(SWIFT_CONFIGURATION) --filter TestCLIRunBase || exit_code=1 ; \ @@ -202,12 +203,19 @@ integration: init-block .PHONY: fmt fmt: swift-fmt update-licenses +.PHONY: check +check: swift-fmt-check check-licenses + .PHONY: swift-fmt SWIFT_SRC = $(shell find . -type f -name '*.swift' -not -path "*/.*" -not -path "*.pb.swift" -not -path "*.grpc.swift" -not -path "*/checkouts/*") swift-fmt: @echo Applying the standard code formatting... @$(SWIFT) format --recursive --configuration .swift-format -i $(SWIFT_SRC) +swift-fmt-check: + @echo Applying the standard code formatting... + @$(SWIFT) format lint --recursive --strict --configuration .swift-format-nolint $(SWIFT_SRC) + .PHONY: update-licenses update-licenses: @echo Updating license headers... @@ -220,6 +228,15 @@ check-licenses: @./scripts/ensure-hawkeye-exists.sh @.local/bin/hawkeye check --fail-if-unknown +.PHONY: pre-commit +pre-commit: + cp Scripts/pre-commit.fmt .git/hooks + touch .git/hooks/pre-commit + cat .git/hooks/pre-commit | grep -v 'hooks/pre-commit\.fmt' > /tmp/pre-commit.new || true + echo 'PRECOMMIT_NOFMT=$${PRECOMMIT_NOFMT} $$(git rev-parse --show-toplevel)/.git/hooks/pre-commit.fmt' >> /tmp/pre-commit.new + mv /tmp/pre-commit.new .git/hooks/pre-commit + chmod +x .git/hooks/pre-commit + .PHONY: serve-docs serve-docs: @echo 'to browse: open http://127.0.0.1:8000/container/documentation/' diff --git a/Tests/CLITests/Subcommands/Run/TestCLIRunCommand.swift b/Tests/CLITests/Subcommands/Run/TestCLIRunCommand.swift index 0f7cb1ea..9fb9cd8e 100644 --- a/Tests/CLITests/Subcommands/Run/TestCLIRunCommand.swift +++ b/Tests/CLITests/Subcommands/Run/TestCLIRunCommand.swift @@ -401,6 +401,16 @@ class TestCLIRunCommand2: CLITest { return } } +} + +class TestCLIRunCommand3: CLITest { + func getTestName() -> String { + Test.current!.name.trimmingCharacters(in: ["(", ")"]).lowercased() + } + + func getLowercasedTestName() -> String { + getTestName().lowercased() + } @Test func testRunCommandDefaultResolvConf() throws { do { diff --git a/scripts/pre-commit.fmt b/scripts/pre-commit.fmt new file mode 100755 index 00000000..58c96269 --- /dev/null +++ b/scripts/pre-commit.fmt @@ -0,0 +1,16 @@ +#! /bin/bash -e + +setup_error() { + echo failed to run: $1 1>&2 + echo run '"make pre-commit"' and try again 1>&2 + exit 1 +} + +if [ ! -z "${PRECOMMIT_NOFMT}" ] ; then + exit 0 +fi + +echo checking formatting and licenses 1>&2 +project_pathname=$(git rev-parse --show-toplevel) +cd "${project_pathname}" +make check