diff --git a/.github/workflows/main.yml b/.github/workflows/main.yml index 71cd104..2f84f9f 100644 --- a/.github/workflows/main.yml +++ b/.github/workflows/main.yml @@ -36,18 +36,33 @@ concurrency: cancel-in-progress: true jobs: - swiftlint: - name: SwiftLint - runs-on: macos-14 + # lint_code: + # name: Lint Code + # runs-on: macos-14 + # steps: + # - name: Checkout + # uses: actions/checkout@v4 + # - name: Install SwiftLint + # run: brew install swiftlint + # - name: Lint + # run: | + # set -o pipefail + # swiftlint lint --quiet | sed -E 's/^(.*):([0-9]+):([0-9]+): (warning|error|[^:]+): (.*)/::\4 title=Lint error,file=\1,line=\2,col=\3::\5\n\1:\2:\3/' + + lint_code: + name: Lint Code + runs-on: ubuntu-latest steps: - name: Checkout uses: actions/checkout@v4 - - name: Install SwiftLint - run: brew install swiftlint + with: + fetch-depth: 0 # Full git history is needed to get a proper list of changed files - name: Lint - run: | - set -o pipefail - swiftlint lint --quiet | sed -E 's/^(.*):([0-9]+):([0-9]+): (warning|error|[^:]+): (.*)/::\4 title=Lint error,file=\1,line=\2,col=\3::\5\n\1:\2:\3/' + uses: mtgto/swift-format-action@main + with: + configuration_file: .swift-format # Please comment out if you won't specify configuration file + all_files: false # default is false + max_warnings: -1 # default is -1 (infinity) ### ref: https://github.com/Alamofire/Alamofire/blob/master/.github/workflows/ci.yml test_macOS: @@ -96,7 +111,7 @@ jobs: token: ${{ secrets.CODECOV_TOKEN }} fail_ci_if_error: true verbose: true - needs: swiftlint + needs: lint_code test_Catalyst: name: ${{ matrix.name }} @@ -118,7 +133,7 @@ jobs: - uses: actions/checkout@v4 - name: Catalyst run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "Punycode.xcodeproj" -scheme "Punycode" -destination "platform=macOS" clean test 2>&1 | xcbeautify --renderer github-actions - needs: swiftlint + needs: lint_code test_iOS: name: ${{ matrix.name }} @@ -149,7 +164,7 @@ jobs: - uses: actions/checkout@v4 - name: ${{ matrix.name }} run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "Punycode.xcodeproj" -scheme "Punycode" -destination "${{ matrix.destination }}" clean test 2>&1 | xcbeautify --renderer github-actions - needs: swiftlint + needs: lint_code test_tvOS: name: ${{ matrix.name }} @@ -180,7 +195,7 @@ jobs: - uses: actions/checkout@v4 - name: ${{ matrix.name }} run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "Punycode.xcodeproj" -scheme "Punycode" -destination "${{ matrix.destination }}" clean test 2>&1 | xcbeautify --renderer github-actions - needs: swiftlint + needs: lint_code test_visionOS: name: ${{ matrix.name }} @@ -202,7 +217,7 @@ jobs: - uses: actions/checkout@v4 - name: ${{ matrix.name }} run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "Punycode.xcodeproj" -scheme "${{ matrix.scheme }}" -destination "${{ matrix.destination }}" clean test 2>&1 | xcbeautify --renderer github-actions - needs: swiftlint + needs: lint_code test_watchOS: name: ${{ matrix.name }} @@ -233,7 +248,7 @@ jobs: - uses: actions/checkout@v4 - name: ${{ matrix.name }} run: set -o pipefail && env NSUnbufferedIO=YES xcodebuild -project "Punycode.xcodeproj" -scheme "Punycode" -destination "${{ matrix.destination }}" clean test 2>&1 | xcbeautify --renderer github-actions - needs: swiftlint + needs: lint_code SPM: name: ${{ matrix.name }} @@ -307,7 +322,7 @@ jobs: steps: - name: Checkout uses: actions/checkout@v4 - - name: Run SwiftLint and Build Carthage for ${{ matrix.platform }} + - name: Build Carthage for ${{ matrix.platform }} run: | carthage build \ --no-skip-current \ diff --git a/.hound.yml b/.hound.yml new file mode 100644 index 0000000..b0e7e79 --- /dev/null +++ b/.hound.yml @@ -0,0 +1,3 @@ +swift: + enabled: true + config_file: .swift-format diff --git a/.swift-format b/.swift-format new file mode 100644 index 0000000..69d80ff --- /dev/null +++ b/.swift-format @@ -0,0 +1,69 @@ +{ + "fileScopedDeclarationPrivacy" : { + "accessLevel" : "private" + }, + "indentation" : { + "spaces" : 4 + }, + "indentConditionalCompilationBlocks" : false, + "indentSwitchCaseLabels" : false, + "lineBreakAroundMultilineExpressionChainComponents" : false, + "lineBreakBeforeControlFlowKeywords" : false, + "lineBreakBeforeEachArgument" : true, + "lineBreakBeforeEachGenericRequirement" : false, + "lineLength" : 200, + "maximumBlankLines" : 1, + "multiElementCollectionTrailingCommas" : true, + "noAssignmentInExpressions" : { + "allowedFunctions" : [ + "XCTAssertNoThrow" + ] + }, + "prioritizeKeepingFunctionOutputTogether" : false, + "respectsExistingLineBreaks" : true, + "rules" : { + "AllPublicDeclarationsHaveDocumentation" : false, + "AlwaysUseLiteralForEmptyCollectionInit" : false, + "AlwaysUseLowerCamelCase" : true, + "AmbiguousTrailingClosureOverload" : true, + "BeginDocumentationCommentWithOneLineSummary" : false, + "DoNotUseSemicolons" : true, + "DontRepeatTypeInStaticProperties" : true, + "FileScopedDeclarationPrivacy" : true, + "FullyIndirectEnum" : true, + "GroupNumericLiterals" : true, + "IdentifiersMustBeASCII" : true, + "NeverForceUnwrap" : false, + "NeverUseForceTry" : false, + "NeverUseImplicitlyUnwrappedOptionals" : false, + "NoAccessLevelOnExtensionDeclaration" : false, + "NoAssignmentInExpressions" : true, + "NoBlockComments" : false, + "NoCasesWithOnlyFallthrough" : true, + "NoEmptyTrailingClosureParentheses" : true, + "NoLabelsInCasePatterns" : true, + "NoLeadingUnderscores" : false, + "NoParensAroundConditions" : true, + "NoPlaygroundLiterals" : true, + "NoVoidReturnOnFunctionSignature" : true, + "OmitExplicitReturns" : false, + "OneCasePerLine" : true, + "OneVariableDeclarationPerLine" : true, + "OnlyOneTrailingClosureArgument" : true, + "OrderedImports" : true, + "ReplaceForEachWithForLoop" : true, + "ReturnVoidInsteadOfEmptyTuple" : true, + "TypeNamesShouldBeCapitalized" : true, + "UseEarlyExits" : false, + "UseLetInEveryBoundCaseVariable" : false, + "UseShorthandTypeNames" : true, + "UseSingleLinePropertyGetter" : true, + "UseSynthesizedInitializer" : false, + "UseTripleSlashForDocumentationComments" : true, + "UseWhereClausesInForLoops" : false, + "ValidateDocumentationComments" : false + }, + "spacesAroundRangeFormationOperators" : false, + "tabWidth" : 4, + "version" : 1 +} diff --git a/.swiftlint.yml b/.swiftlint.yml index 1272542..518e707 100644 --- a/.swiftlint.yml +++ b/.swiftlint.yml @@ -1,18 +1,13 @@ -# at v0.6.0 disabled_rules: - - identifier_name - trailing_whitespace - todo -line_length: 480 +line_length: 960 function_body_length: 100 -identifier_name: - excluded: - - id - - ok - excluded: + - Source/SPMPSL.swift + - Tests - Carthage - Pods - fastlane diff --git a/Punycode.xcodeproj/project.pbxproj b/Punycode.xcodeproj/project.pbxproj index c726488..19dbd51 100644 --- a/Punycode.xcodeproj/project.pbxproj +++ b/Punycode.xcodeproj/project.pbxproj @@ -109,6 +109,7 @@ isa = PBXNativeTarget; buildConfigurationList = A75359C12C7ABA7D00564274 /* Build configuration list for PBXNativeTarget "Punycode" */; buildPhases = ( + A719B8B92C7C7F8E008E89EF /* Lint codes with swift-format */, A75359AA2C7ABA7C00564274 /* Headers */, A75359AB2C7ABA7C00564274 /* Sources */, A75359AC2C7ABA7C00564274 /* Frameworks */, @@ -196,6 +197,28 @@ }; /* End PBXResourcesBuildPhase section */ +/* Begin PBXShellScriptBuildPhase section */ + A719B8B92C7C7F8E008E89EF /* Lint codes with swift-format */ = { + isa = PBXShellScriptBuildPhase; + alwaysOutOfDate = 1; + buildActionMask = 2147483647; + files = ( + ); + inputFileListPaths = ( + ); + inputPaths = ( + ); + name = "Lint codes with swift-format"; + outputFileListPaths = ( + ); + outputPaths = ( + ); + runOnlyForDeploymentPostprocessing = 0; + shellPath = /bin/zsh; + shellScript = "export PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which swift-format >/dev/null; then\n swift-format format --in-place --ignore-unparsable-files --configuration .swift-format --recursive Source Tests || true\n swift-format lint --ignore-unparsable-files --configuration .swift-format --recursive Source Tests || true\nelse\n echo \"warning: swift-format not installed\"\nfi\n"; + }; +/* End PBXShellScriptBuildPhase section */ + /* Begin PBXSourcesBuildPhase section */ A75359AB2C7ABA7C00564274 /* Sources */ = { isa = PBXSourcesBuildPhase; diff --git a/Source/Punycode.swift b/Source/Punycode.swift index 3c383e9..27d6145 100755 --- a/Source/Punycode.swift +++ b/Source/Punycode.swift @@ -48,7 +48,7 @@ public class Puny { if lowercase.contains(character) { return Int(character.unicodeScalars.first!.value - lettersBase) } else if digits.contains(character) { - return Int(character.unicodeScalars.first!.value - digitsBase) + 26 /// count of lowercase letters range + return Int(character.unicodeScalars.first!.value - digitsBase) + 26/// count of lowercase letters range } else { return nil } @@ -88,7 +88,7 @@ public class Puny { repeat { let character: Character = punycodeInput.removeFirst() guard let digit: Int = punycodeIndex(for: character) else { - return nil /// Failing on badly formatted punycode + return nil/// Failing on badly formatted punycode } i += digit * w let t = k <= bias ? tMin : (k >= bias + tMax ? tMax : k - bias) @@ -125,7 +125,7 @@ public class Puny { let char = Character(scalar) output.append(char) } else if !scalar.isValid { - return nil /// Encountered a scalar out of acceptable range + return nil/// Encountered a scalar out of acceptable range } } var handled: Int = output.count diff --git a/Tests/PunycodeTests.swift b/Tests/PunycodeTests.swift index 4e8c474..db15233 100755 --- a/Tests/PunycodeTests.swift +++ b/Tests/PunycodeTests.swift @@ -14,18 +14,27 @@ final class PunycodeTests: XCTestCase { let egyptian: String = "\u{0644}\u{064A}\u{0647}\u{0645}\u{0627}\u{0628}\u{062A}\u{0643}\u{0644}\u{0645}\u{0648}\u{0634}\u{0639}\u{0631}\u{0628}\u{064A}\u{061F}" let chineseSimplified: String = "\u{4ED6}\u{4EEC}\u{4E3A}\u{4EC0}\u{4E48}\u{4E0D}\u{8BF4}\u{4E2D}\u{6587}" let chineseTraditional: String = "\u{4ED6}\u{5011}\u{7232}\u{4EC0}\u{9EBD}\u{4E0D}\u{8AAA}\u{4E2D}\u{6587}" - let czech: String = "\u{0050}\u{0072}\u{006F}\u{010D}\u{0070}\u{0072}\u{006F}\u{0073}\u{0074}\u{011B}\u{006E}\u{0065}\u{006D}\u{006C}\u{0075}\u{0076}\u{00ED}\u{010D}\u{0065}\u{0073}\u{006B}\u{0079}" - let hebrew: String = "\u{05DC}\u{05DE}\u{05D4}\u{05D4}\u{05DD}\u{05E4}\u{05E9}\u{05D5}\u{05D8}\u{05DC}\u{05D0}\u{05DE}\u{05D3}\u{05D1}\u{05E8}\u{05D9}\u{05DD}\u{05E2}\u{05D1}\u{05E8}\u{05D9}\u{05EA}" - let hindi: String = "\u{092F}\u{0939}\u{0932}\u{094B}\u{0917}\u{0939}\u{093F}\u{0928}\u{094D}\u{0926}\u{0940}\u{0915}\u{094D}\u{092F}\u{094B}\u{0902}\u{0928}\u{0939}\u{0940}\u{0902}\u{092C}\u{094B}\u{0932}\u{0938}\u{0915}\u{0924}\u{0947}\u{0939}\u{0948}\u{0902}" + let czech: String = + "\u{0050}\u{0072}\u{006F}\u{010D}\u{0070}\u{0072}\u{006F}\u{0073}\u{0074}\u{011B}\u{006E}\u{0065}\u{006D}\u{006C}\u{0075}\u{0076}\u{00ED}\u{010D}\u{0065}\u{0073}\u{006B}\u{0079}" + let hebrew: String = + "\u{05DC}\u{05DE}\u{05D4}\u{05D4}\u{05DD}\u{05E4}\u{05E9}\u{05D5}\u{05D8}\u{05DC}\u{05D0}\u{05DE}\u{05D3}\u{05D1}\u{05E8}\u{05D9}\u{05DD}\u{05E2}\u{05D1}\u{05E8}\u{05D9}\u{05EA}" + let hindi: String = + "\u{092F}\u{0939}\u{0932}\u{094B}\u{0917}\u{0939}\u{093F}\u{0928}\u{094D}\u{0926}\u{0940}\u{0915}\u{094D}\u{092F}\u{094B}\u{0902}\u{0928}\u{0939}\u{0940}\u{0902}\u{092C}\u{094B}\u{0932}\u{0938}\u{0915}\u{0924}\u{0947}\u{0939}\u{0948}\u{0902}" let japanese: String = "\u{306A}\u{305C}\u{307F}\u{3093}\u{306A}\u{65E5}\u{672C}\u{8A9E}\u{3092}\u{8A71}\u{3057}\u{3066}\u{304F}\u{308C}\u{306A}\u{3044}\u{306E}\u{304B}" - let korean: String = "\u{C138}\u{ACC4}\u{C758}\u{BAA8}\u{B4E0}\u{C0AC}\u{B78C}\u{B4E4}\u{C774}\u{D55C}\u{AD6D}\u{C5B4}\u{B97C}\u{C774}\u{D574}\u{D55C}\u{B2E4}\u{BA74}\u{C5BC}\u{B9C8}\u{B098}\u{C88B}\u{C744}\u{AE4C}" - let russian: String = "\u{043F}\u{043E}\u{0447}\u{0435}\u{043C}\u{0443}\u{0436}\u{0435}\u{043E}\u{043D}\u{0438}\u{043D}\u{0435}\u{0433}\u{043E}\u{0432}\u{043E}\u{0440}\u{044F}\u{0442}\u{043F}\u{043E}\u{0440}\u{0443}\u{0441}\u{0441}\u{043A}\u{0438}" - let spanish: String = "\u{0050}\u{006F}\u{0072}\u{0071}\u{0075}\u{00E9}\u{006E}\u{006F}\u{0070}\u{0075}\u{0065}\u{0064}\u{0065}\u{006E}\u{0073}\u{0069}\u{006D}\u{0070}\u{006C}\u{0065}\u{006D}\u{0065}\u{006E}\u{0074}\u{0065}\u{0068}\u{0061}\u{0062}\u{006C}\u{0061}\u{0072}\u{0065}\u{006E}\u{0045}\u{0073}\u{0070}\u{0061}\u{00F1}\u{006F}\u{006C}" - let vietnamese: String = "\u{0054}\u{1EA1}\u{0069}\u{0073}\u{0061}\u{006F}\u{0068}\u{1ECD}\u{006B}\u{0068}\u{00F4}\u{006E}\u{0067}\u{0074}\u{0068}\u{1EC3}\u{0063}\u{0068}\u{1EC9}\u{006E}\u{00F3}\u{0069}\u{0074}\u{0069}\u{1EBF}\u{006E}\u{0067}\u{0056}\u{0069}\u{1EC7}\u{0074}" + let korean: String = + "\u{C138}\u{ACC4}\u{C758}\u{BAA8}\u{B4E0}\u{C0AC}\u{B78C}\u{B4E4}\u{C774}\u{D55C}\u{AD6D}\u{C5B4}\u{B97C}\u{C774}\u{D574}\u{D55C}\u{B2E4}\u{BA74}\u{C5BC}\u{B9C8}\u{B098}\u{C88B}\u{C744}\u{AE4C}" + let russian: String = + "\u{043F}\u{043E}\u{0447}\u{0435}\u{043C}\u{0443}\u{0436}\u{0435}\u{043E}\u{043D}\u{0438}\u{043D}\u{0435}\u{0433}\u{043E}\u{0432}\u{043E}\u{0440}\u{044F}\u{0442}\u{043F}\u{043E}\u{0440}\u{0443}\u{0441}\u{0441}\u{043A}\u{0438}" + let spanish: String = + "\u{0050}\u{006F}\u{0072}\u{0071}\u{0075}\u{00E9}\u{006E}\u{006F}\u{0070}\u{0075}\u{0065}\u{0064}\u{0065}\u{006E}\u{0073}\u{0069}\u{006D}\u{0070}\u{006C}\u{0065}\u{006D}\u{0065}\u{006E}\u{0074}\u{0065}\u{0068}\u{0061}\u{0062}\u{006C}\u{0061}\u{0072}\u{0065}\u{006E}\u{0045}\u{0073}\u{0070}\u{0061}\u{00F1}\u{006F}\u{006C}" + let vietnamese: String = + "\u{0054}\u{1EA1}\u{0069}\u{0073}\u{0061}\u{006F}\u{0068}\u{1ECD}\u{006B}\u{0068}\u{00F4}\u{006E}\u{0067}\u{0074}\u{0068}\u{1EC3}\u{0063}\u{0068}\u{1EC9}\u{006E}\u{00F3}\u{0069}\u{0074}\u{0069}\u{1EBF}\u{006E}\u{0067}\u{0056}\u{0069}\u{1EC7}\u{0074}" let jBlockL: String = "\u{0033}\u{5E74}\u{0042}\u{7D44}\u{91D1}\u{516B}\u{5148}\u{751F}" - let jBlockM: String = "\u{5B89}\u{5BA4}\u{5948}\u{7F8E}\u{6075}\u{002D}\u{0077}\u{0069}\u{0074}\u{0068}\u{002D}\u{0053}\u{0055}\u{0050}\u{0045}\u{0052}\u{002D}\u{004D}\u{004F}\u{004E}\u{004B}\u{0045}\u{0059}\u{0053}" - let jBlockN: String = "\u{0048}\u{0065}\u{006C}\u{006C}\u{006F}\u{002D}\u{0041}\u{006E}\u{006F}\u{0074}\u{0068}\u{0065}\u{0072}\u{002D}\u{0057}\u{0061}\u{0079}\u{002D}\u{305D}\u{308C}\u{305E}\u{308C}\u{306E}\u{5834}\u{6240}" + let jBlockM: String = + "\u{5B89}\u{5BA4}\u{5948}\u{7F8E}\u{6075}\u{002D}\u{0077}\u{0069}\u{0074}\u{0068}\u{002D}\u{0053}\u{0055}\u{0050}\u{0045}\u{0052}\u{002D}\u{004D}\u{004F}\u{004E}\u{004B}\u{0045}\u{0059}\u{0053}" + let jBlockN: String = + "\u{0048}\u{0065}\u{006C}\u{006C}\u{006F}\u{002D}\u{0041}\u{006E}\u{006F}\u{0074}\u{0068}\u{0065}\u{0072}\u{002D}\u{0057}\u{0061}\u{0079}\u{002D}\u{305D}\u{308C}\u{305E}\u{308C}\u{306E}\u{5834}\u{6240}" let jBlockO: String = "\u{3072}\u{3068}\u{3064}\u{5C4B}\u{6839}\u{306E}\u{4E0B}\u{0032}" let jBlockP: String = "\u{004D}\u{0061}\u{006A}\u{0069}\u{3067}\u{004B}\u{006F}\u{0069}\u{3059}\u{308B}\u{0035}\u{79D2}\u{524D}" let jBlockQ: String = "\u{30D1}\u{30D5}\u{30A3}\u{30FC}\u{0064}\u{0065}\u{30EB}\u{30F3}\u{30D0}" @@ -121,45 +130,45 @@ final class PunycodeTests: XCTestCase { XCTAssertNoThrow(invalidPunycode.idnaDecoded) } -// func testFoo1() { -// var sushi: String = "寿司" -// -// sushi = sushi.idnaEncoded! -// print(sushi) // xn--sprr0q -// -// sushi = sushi.idnaDecoded! -// print(sushi) // "寿司" -// } -// -// func testFoo2() { -// var sushi: String = "寿司" -// -// sushi = sushi.punycodeEncoded! -// print(sushi) // sprr0q -// -// sushi = sushi.punycodeDecoded! -// print(sushi) // "寿司" -// } -// -// func testFoo3() { -// var sushi: Substring = "寿司大好き".prefix(2) -// print(sushi) // "寿司" -// -// var sushiStr = sushi.idnaEncoded! -// print(sushiStr) // xn--sprr0q -// -// sushiStr = sushiStr.idnaDecoded! -// print(sushiStr) // "寿司" -// } -// -// func testFoo4() { -// var sushi: Substring = "寿司大好き".prefix(2) -// print(sushi) // "寿司" -// -// var sushiStr = sushi.punycodeEncoded! -// print(sushiStr) // sprr0q -// -// sushiStr = sushiStr.punycodeDecoded! -// print(sushiStr) // "寿司" -// } + // func testFoo1() { + // var sushi: String = "寿司" + // + // sushi = sushi.idnaEncoded! + // print(sushi) // xn--sprr0q + // + // sushi = sushi.idnaDecoded! + // print(sushi) // "寿司" + // } + // + // func testFoo2() { + // var sushi: String = "寿司" + // + // sushi = sushi.punycodeEncoded! + // print(sushi) // sprr0q + // + // sushi = sushi.punycodeDecoded! + // print(sushi) // "寿司" + // } + // + // func testFoo3() { + // var sushi: Substring = "寿司大好き".prefix(2) + // print(sushi) // "寿司" + // + // var sushiStr = sushi.idnaEncoded! + // print(sushiStr) // xn--sprr0q + // + // sushiStr = sushiStr.idnaDecoded! + // print(sushiStr) // "寿司" + // } + // + // func testFoo4() { + // var sushi: Substring = "寿司大好き".prefix(2) + // print(sushi) // "寿司" + // + // var sushiStr = sushi.punycodeEncoded! + // print(sushiStr) // sprr0q + // + // sushiStr = sushiStr.punycodeDecoded! + // print(sushiStr) // "寿司" + // } } diff --git a/fastlane/Fastfile b/fastlane/Fastfile index a70ec7e..2f4b01f 100644 --- a/fastlane/Fastfile +++ b/fastlane/Fastfile @@ -19,12 +19,22 @@ scheme = "Punycode" desc "Run all jobs" lane :run_all do + lint_swift tests build_spm build_carthage lint_cocoapods end +########################################## +# Lint +########################################## + +desc "Lint codes with swift-format" +lane :lint_swift do + # sh("swift-format", "format", "--in-place", "--ignore-unparsable-files", "--configuration", "../.swift-format", "--recursive", "../Source", "../Tests") + sh("swift-format", "lint", "--ignore-unparsable-files", "--configuration", "../.swift-format", "--recursive", "../Source", "../Tests") +end ########################################## # Versioning diff --git a/run.sh b/run.sh index f9036d7..d2c8ee9 100755 --- a/run.sh +++ b/run.sh @@ -8,6 +8,7 @@ fi local option_list=( "fastlane run_all" + "fastlane lint_swift" "fastlane tests" "fastlane build_spm" "fastlane build_carthage"