Lint anything by combining the power of Swift & regular expressions.

Overview

CI Code Quality Coverage Version: 0.8.2 License: MIT
PayPal: Donate GitHub: Become a sponsor Patreon: Become a patron

InstallationGetting StartedConfigurationXcode Build ScriptDonationIssuesRegex Cheat SheetLicense

AnyLint

Lint any project in any language using Swift and regular expressions. With built-in support for matching and non-matching examples validation & autocorrect replacement. Replaces SwiftLint custom rules & works for other languages as well! 🎉

Installation

Via Homebrew:

To install AnyLint the first time, run these commands:

brew tap Flinesoft/AnyLint https://github.com/Flinesoft/AnyLint.git
brew install anylint

To update it to the latest version, run this instead:

brew upgrade anylint

Via Mint:

To install AnyLint or update to the latest version, run this command:

mint install Flinesoft/AnyLint

Getting Started

To initialize AnyLint in a project, run:

anylint --init blank

This will create the Swift script file lint.swift with something like the following contents:

#!/usr/local/bin/swift-sh
import AnyLint // @Flinesoft

Lint.logSummaryAndExit(arguments: CommandLine.arguments) {
    // MARK: - Variables
    let readmeFile: Regex = #"README\.md"#

    // MARK: - Checks
    // MARK: Readme
    try Lint.checkFilePaths(
        checkInfo: "Readme: Each project should have a README.md file explaining the project.",
        regex: readmeFile,
        matchingExamples: ["README.md"],
        nonMatchingExamples: ["README.markdown", "Readme.md", "ReadMe.md"],
        violateIfNoMatchesFound: true
    )

    // MARK: ReadmeTypoLicense
    try Lint.checkFileContents(
        checkInfo: "ReadmeTypoLicense: Misspelled word 'license'.",
        regex: #"([\s#]L|l)isence([\s\.,:;])"#,
        matchingExamples: [" license:", "## Lisence\n"],
        nonMatchingExamples: [" license:", "## License\n"],
        includeFilters: [readmeFile],
        autoCorrectReplacement: "$1icense$2",
        autoCorrectExamples: [
            ["before": " lisence:", "after": " license:"],
            ["before": "## Lisence\n", "after": "## License\n"],
        ]
    )
}

The most important thing to note is that the first three lines are required for AnyLint to work properly.

All the other code can be adjusted and that's actually where you configure your lint checks (a few examples are provided by default in the blank template). Note that the first two lines declare the file to be a Swift script using swift-sh. Thus, you can run any Swift code and even import Swift packages (see the swift-sh docs) if you need to. The third line makes sure that all violations found in the process of running the code in the completion block are reported properly and exits the script with the proper exit code at the end.

Having this configuration file, you can now run anylint to run your lint checks. By default, if any check fails, the entire command fails and reports the violation reason. To learn more about how to configure your own checks, see the Configuration section below.

If you want to create and run multiple configuration files or if you want a different name or location for the default config file, you can pass the --path option, which can be used multiple times as well like this:

Initializes the configuration files at the given locations:

anylint --init blank --path Sources/lint.swift --path Tests/lint.swift

Runs the lint checks for both configuration files:

anylint --path Sources/lint.swift --path Tests/lint.swift

There are also several flags you can pass to anylint:

  1. -s / --strict: Fails on warnings as well. (By default, the command only fails on errors.)
  2. -x / --xcode: Prints warnings & errors in a format to be reported right within Xcodes left sidebar.
  3. -l / --validate: Runs only validations for matchingExamples, nonMatchingExamples and autoCorrectExamples.
  4. -v / --version: Prints the current tool version. (Does not run any lint checks.)
  5. -d / --debug: Logs much more detailed information about what AnyLint is doing for debugging purposes.

Configuration

AnyLint provides three different kinds of lint checks:

  1. checkFileContents: Matches the contents of a text file to a given regex.
  2. checkFilePaths: Matches the file paths of the current directory to a given regex.
  3. customCheck: Allows to write custom Swift code to do other kinds of checks.

Several examples of lint checks can be found in the lint.swift file of this very project.

Basic Types

Independent from the method used, there are a few types specified in the AnyLint package you should know of.

Regex

Many parameters in the above mentioned lint check methods are of Regex type. A Regex can be initialized in several ways:

  1. Using a String:
let regex = Regex(#"(foo|bar)[0-9]+"#) // => /(foo|bar)[0-9]+/
let regexWithOptions = Regex(#"(foo|bar)[0-9]+"#, options: [.ignoreCase, .dotMatchesLineSeparators, .anchorsMatchLines]) // => /(foo|bar)[0-9]+/im
  1. Using a String Literal:
let regex: Regex = #"(foo|bar)[0-9]+"#  // => /(foo|bar)[0-9]+/
let regexWithOptions: Regex = #"(foo|bar)[0-9]+\im"#  // => /(foo|bar)[0-9]+/im
  1. Using a Dictionary Literal: (use for named capture groups)
let regex: Regex = ["key": #"foo|bar"#, "num": "[0-9]+"] // => /(?<key>foo|bar)(?<num>[0-9]+)/
let regexWithOptions: Regex = ["key": #"foo|bar"#, "num": "[0-9]+", #"\"#: "im"] // => /(?<key>foo|bar)(?<num>[0-9]+)/im

Note that we recommend using raw strings (#"foo"# instead of "foo") for all regexes to get rid of double escaping backslashes (e.g. \\s becomes \s). This also allows for testing regexes in online regex editors like Rubular first and then copy & pasting from them without any additional escaping (except for { & }, replace with \{ & \}).

Regex Options

Specifying Regex options in literals is done via the \ separator as shown in the examples above. The available options are:

  1. i for .ignoreCase: Any specified characters will both match uppercase and lowercase variants.
  2. m for .dotMatchesLineSeparators: All appearances of . in regexes will also match newlines (which are not matched against by default).

The .anchorsMatchLines option is always activated on literal usage as we strongly recommend it. It ensures that ^ can be used to match the start of a line and $ for the end of a line. By default they would match the start & end of the entire string. If that's actually what you want, you can still use \A and \z for that. This makes the default literal Regex behavior more in line with sites like Rubular.

CheckInfo

A CheckInfo contains the basic information about a lint check. It consists of:

  1. id: The identifier of your lint check. For example: EmptyTodo
  2. hint: The hint explaining the cause of the violation or the steps to fix it.
  3. severity: The severity of violations. One of error, warning, info. Default: error

While there is an initializer available, we recommend using a String Literal instead like so:

// accepted structure: <id>(@<severity>): <hint>
let checkInfo: CheckInfo = "ReadmePath: The README file should be named exactly `README.md`."
let checkInfoCustomSeverity: CheckInfo = "ReadmePath@warning: The README file should be named exactly `README.md`."

AutoCorrection

An AutoCorrection contains an example before and after string to validate that a given autocorrection rule behaves correctly.

It can be initialized in two ways, either with the default initializer:

let example: AutoCorrection = AutoCorrection(before: "Lisence", after: "License")

Or using a Dictionary literal:

let example: AutoCorrection = ["before": "Lisence", "after": "License"]

Check File Contents

AnyLint has rich support for checking the contents of a file using a regex. The design follows the approach "make simple things simple and hard things possible". Thus, let's explain the checkFileContents method with a simple and a complex example.

In its simplest form, the method just requires a checkInfo and a regex:

// MARK: EmptyTodo
try Lint.checkFileContents(
    checkInfo: "EmptyTodo: TODO comments should not be empty.",
    regex: #"// TODO: *\n"#
)

But we strongly recommend to always provide also:

  1. matchingExamples: Array of strings expected to match the given string for regex validation.
  2. nonMatchingExamples: Array of strings not matching the given string for regex validation.
  3. includeFilters: Array of Regex objects to include to the file paths to check.

The first two will be used on each run of AnyLint to check if the provided regex actually works as expected. If any of the matchingExamples doesn't match or if any of the nonMatchingExamples does match, the entire AnyLint command will fail early. This a built-in validation step to help preventing a lot of issues and increasing your confidence on the lint checks.

The third one is recommended because it increases the performance of the linter. Only files at paths matching at least one of the provided regexes will be checked. If not provided, all files within the current directory will be read recursively for each check, which is inefficient.

Here's the recommended minimum example:

// MARK: - Variables
let swiftSourceFiles: Regex = #"Sources/.*\.swift"#
let swiftTestFiles: Regex = #"Tests/.*\.swift"#

// MARK: - Checks
// MARK: empty_todo
try Lint.checkFileContents(
    checkInfo: "EmptyTodo: TODO comments should not be empty.",
    regex: #"// TODO: *\n"#,
    matchingExamples: ["// TODO:\n"],
    nonMatchingExamples: ["// TODO: not yet implemented\n"],
    includeFilters: [swiftSourceFiles, swiftTestFiles]
)

There's 3 more parameters you can optionally set if needed:

  1. excludeFilters: Array of Regex objects to exclude from the file paths to check.
  2. autoCorrectReplacement: Replacement string which can reference any capture groups in the regex.
  3. autoCorrectExamples: Example structs with before and after for autocorrection validation.
  4. repeatIfAutoCorrected: Repeat check if at least one auto-correction was applied in last run. Defaults to false.

The excludeFilters can be used alternatively to the includeFilters or alongside them. If used alongside, exclusion will take precedence over inclusion.

If autoCorrectReplacement is provided, AnyLint will automatically replace matches of regex with the given replacement string. Capture groups are supported, both in numbered style (([a-z]+)(\d+) => $1$2) and named group style ((?<alpha>[a-z])(?<num>\d+) => $alpha$num). When provided, we strongly recommend to also provide autoCorrectExamples for validation. Like for matchingExamples / nonMatchingExamples the entire command will fail early if one of the examples doesn't correct from the before string to the expected after string.

Caution: When using the autoCorrectReplacement parameter, be sure to double-check that your regex doesn't match too much content. Additionally, we strongly recommend to commit your changes regularly to have some backup.

Here's a full example using all parameters at once:

// MARK: - Variables
let swiftSourceFiles: Regex = #"Sources/.*\.swift"#
let swiftTestFiles: Regex = #"Tests/.*\.swift"#

// MARK: - Checks
// MARK: empty_method_body
try Lint.checkFileContents(
    checkInfo: "EmptyMethodBody: Don't use whitespaces for the body of empty methods.",
    regex: [
      "declaration": #"func [^\(\s]+\([^{]*\)"#,
      "spacing": #"\s*"#,
      "body": #"\{\s+\}"#
    ],
    matchingExamples: [
        "func foo2bar()  { }",
        "func foo2bar(x: Int, y: Int)  { }",
        "func foo2bar(\n    x: Int,\n    y: Int\n) {\n    \n}",
    ],
    nonMatchingExamples: [
      "func foo2bar() {}",
      "func foo2bar(x: Int, y: Int) {}"
    ],
    includeFilters: [swiftSourceFiles],
    excludeFilters: [swiftTestFiles],
    autoCorrectReplacement: "$declaration {}",
    autoCorrectExamples: [
        ["before": "func foo2bar()  { }", "after": "func foo2bar() {}"],
        ["before": "func foo2bar(x: Int, y: Int)  { }", "after": "func foo2bar(x: Int, y: Int) {}"],
        ["before": "func foo2bar()\n{\n    \n}", "after": "func foo2bar() {}"],
    ]
)

Note that when autoCorrectReplacement produces a replacement string that exactly matches the matched string of regex, then no violation will be reported. This enables us to provide more generic regex patterns that also match the correct string without actually reporting a violation for the correct one. For example, using the regex if\s*\(([^)]+)\)\s*\{ to check whitespaces around braces after if statement would report a violation for all of the following examples:

if(x == 5) { /* some code */ }
if (x == 5){ /* some code */ }
if(x == 5){ /* some code */ }
if (x == 5) { /* some code */ }

The problem is that the last example actually is our expected formatting and should not violate. By providing an autoCorrectReplacement of if ($1) {, we can fix that as the replacement would be equal to the matched string, so no violation would be reported for the last example and all the others would be auto-corrected – just what we want. 🎉

(The alternative would be to split the check to two separate ones, one fore checking the prefix and one the suffix whitespacing – not so beautiful as this blows up our lint.swift configuration file very quickly.)

Skip file content checks

While the includeFilters and excludeFilters arguments in the config file can be used to skip checks on specified files, sometimes it's necessary to make exceptions and specify that within the files themselves. For example this can become handy when there's a check which works 99% of the time, but there might be the 1% of cases where the check is reporting false positives.

For such cases, there are 2 ways to skip checks within the files themselves:

  1. AnyLint.skipHere: <CheckInfo.ID>: Will skip the specified check(s) on the same line and the next line.
var x: Int = 5 // AnyLint.skipHere: MinVarNameLength

// or

// AnyLint.skipHere: MinVarNameLength
var x: Int = 5
  1. AnyLint.skipInFile: <All or CheckInfo.ID>: Will skip All or specificed check(s) in the entire file.
// AnyLint.skipInFile: MinVarNameLength

var x: Int = 5
var y: Int = 5

or

// AnyLint.skipInFile: All

var x: Int = 5
var y: Int = 5

It is also possible to skip multiple checks at once in a line like so:

// AnyLint.skipHere: MinVarNameLength, LineLength, ColonWhitespaces

Check File Paths

The checkFilePaths method has all the same parameters like the checkFileContents method, so please read the above section to learn more about them. There's only one difference and one additional parameter:

  1. autoCorrectReplacement: Here, this will safely move the file using the path replacement.
  2. violateIfNoMatchesFound: Will report a violation if no matches are found if true. Default: false

As this method is about file paths and not file contents, the autoCorrectReplacement actually also fixes the paths, which corresponds to moving files from the before state to the after state. Note that moving/renaming files here is done safely, which means that if a file already exists at the resulting path, the command will fail.

By default, checkFilePaths will fail if the given regex matches a file. If you want to check for the existence of a file though, you can set violateIfNoMatchesFound to true instead, then the method will fail if it does not match any file.

Custom Checks

AnyLint allows you to do any kind of lint checks (thus its name) as it gives you the full power of the Swift programming language and it's packages ecosystem. The customCheck method needs to be used to profit from this flexibility. And it's actually the simplest of the three methods, consisting of only two parameters:

  1. checkInfo: Provides some general information on the lint check.
  2. customClosure: Your custom logic which produces an array of Violation objects.

Note that the Violation type just holds some additional information on the file, matched string, location in the file and applied autocorrection and that all these fields are optional. It is a simple struct used by the AnyLint reporter for more detailed output, no logic attached. The only required field is the CheckInfo object which caused the violation.

If you want to use regexes in your custom code, you can learn more about how you can match strings with a Regex object on the HandySwift docs (the project, the class was taken from) or read the code documentation comments.

When using the customCheck, you might want to also include some Swift packages for easier file handling or running shell commands. You can do so by adding them at the top of the file like so:

#!/usr/local/bin/swift-sh
import AnyLint // @Flinesoft
import ShellOut // @JohnSundell

Lint.logSummaryAndExit(arguments: CommandLine.arguments) {
    // MARK: - Variables
    let projectName: String = "AnyLint"

    // MARK: - Checks
    // MARK: LinuxMainUpToDate
    try Lint.customCheck(checkInfo: "LinuxMainUpToDate: The tests in Tests/LinuxMain.swift should be up-to-date.") { checkInfo in
        var violations: [Violation] = []

        let linuxMainFilePath = "Tests/LinuxMain.swift"
        let linuxMainContentsBeforeRegeneration = try! String(contentsOfFile: linuxMainFilePath)

        let sourceryDirPath = ".sourcery"
        try! shellOut(to: "sourcery", arguments: ["--sources", "Tests/\(projectName)Tests", "--templates", "\(sourceryDirPath)/LinuxMain.stencil", "--output", sourceryDirPath])

        let generatedLinuxMainFilePath = "\(sourceryDirPath)/LinuxMain.generated.swift"
        let linuxMainContentsAfterRegeneration = try! String(contentsOfFile: generatedLinuxMainFilePath)

        // move generated file to LinuxMain path to update its contents
        try! shellOut(to: "mv", arguments: [generatedLinuxMainFilePath, linuxMainFilePath])

        if linuxMainContentsBeforeRegeneration != linuxMainContentsAfterRegeneration {
            violations.append(
                Violation(
                    checkInfo: checkInfo,
                    filePath: linuxMainFilePath,
                    appliedAutoCorrection: AutoCorrection(
                        before: linuxMainContentsBeforeRegeneration,
                        after: linuxMainContentsAfterRegeneration
                    )
                )
            )
        }

        return violations
    }
}

Xcode Build Script

If you are using AnyLint for a project in Xcode, you can configure a build script to run it on each build. In order to do this select your target, choose the Build Phases tab and click the + button on the top left corner of that pane. Select New Run Script Phase and copy the following into the text box below the Shell: /bin/sh of your new run script phase:

if which anylint > /dev/null; then
    anylint -x
else
    echo "warning: AnyLint not installed, download it from https://github.com/Flinesoft/AnyLint"
fi

Next, make sure the AnyLint script runs before the steps Compiling Sources by moving it per drag & drop, for example right after Dependencies. You probably also want to rename it to somethng like AnyLint.

Note: There's a known bug when the build script is used in non-macOS platforms targets.

Regex Cheat Sheet

Refer to the Regex quick reference on rubular.com which all apply for Swift as well:

In Swift, there are some differences to regexes in Ruby (which rubular.com is based on) – take care when copying regexes:

  1. In Ruby, forward slashes (/) must be escaped (\/), that's not necessary in Swift.
  2. In Swift, curly braces ({ & }) must be escaped (\{ & \}), that's not necessary in Ruby.

Here are some advanced Regex features you might want to use or learn more about:

  1. Back references can be used within regexes to match previous capture groups.

    For example, you can make sure that the PR number and link match in PR: [#100](https://github.com/Flinesoft/AnyLint/pull/100) by using a capture group ((\d+)) and a back reference (\1) like in: \[#(\d+)\]\(https://[^)]+/pull/\1\).

    Learn more

  2. Negative & positive lookaheads & lookbehinds allow you to specify patterns with some limitations that will be excluded from the matched range. They are specified with (?=PATTERN) (positive lookahead), (?!PATTERN) (negative lookahead), (?<=PATTERN) (positive lookbehind) or (?<!PATTERN) (negative lookbehind).

    For example, you could use the regex - (?!None\.).* to match any entry in a CHANGELOG.md file except empty ones called None..

    Learn more

  3. Specifically you can use a lookbehind to make sure that the reported line of a regex spanning multiple lines only reports on the exact line where the developer needs to make a change, instead of one line before. That works because the pattern matched by a lookbehind is not considered part of the matching range.

    For example, consider a regex violating if there's an empty line after an opening curly brace like so: {\n\s*\n\s*\S. This would match the lines of func do() {\n\n return 5}, but what you actually want is it to start matching on the empty newline like so: (?<={\n)\s*\n\s*\S.

    See also #3

Donation

AnyLint was brought to you by Cihat Gündüz in his free time. If you want to thank me and support the development of this project, please make a small donation on PayPal. In case you also like my other open source contributions and articles, please consider motivating me by becoming a sponsor on GitHub or a patron on Patreon.

Thank you very much for any donation, it really helps out a lot! 💯

Contributing

Contributions are welcome. Feel free to open an issue on GitHub with your ideas or implement an idea yourself and post a pull request. If you want to contribute code, please try to follow the same syntax and semantic in your commit messages (see rationale here). Also, please make sure to add an entry to the CHANGELOG.md file which explains your change.

License

This library is released under the MIT License. See LICENSE for details.

Comments
  • Report violations as on warning level by default

    Report violations as on warning level by default

    Problem Statement

    Currently it's error which makes builds fail when running via Xcode build script. And there's not even an option like --lenient to downgrade errors to warnings.

    Suggested Solution

    Just default to the warning level instead of error like now.

    enhancement 
    opened by Jeehut 1
  • Wrong swift-sh path at file header

    Wrong swift-sh path at file header

    Expected Behavior

    When running the -init command, I expect that the resulting lint.swift file points to the correct path of swift-sh. The correct path should be automatically detected.

    Actual Behavior

    Currently, it's pointing to the /usr directory, but in latest Homebrew it's placed under /opt.

    Steps to Reproduce the Problem

    1. Install AnyLint
    2. Install Swift-SH
    3. Run anylint --init blank

    Specifications

    • Version: 0.8.3
    • Platform: macOS 12.3
    • IDE Version: Xcode 13.3
    bug 
    opened by Jeehut 1
  • Fails with

    Fails with "Duplicate keys of type 'SearchOptions' were found in a Dictionary"

    Expected Behavior

    Not to fail when building even on Linux servers.

    Actual Behavior

    Fails with this error message sometimes:

    + source /root/.bash_profile
    ++ export SWIFTENV_ROOT=/root/.swiftenv
    ++ SWIFTENV_ROOT=/root/.swiftenv
    ++ export PATH=/root/.swiftenv/bin:/root/.bitrise/tools:/bitrise/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/opt/android-sdk-linux/tools:/opt/android-sdk-linux/tools/bin:/opt/android-sdk-linux/platform-tools:/opt/android-ndk
    ++ PATH=/root/.swiftenv/bin:/root/.bitrise/tools:/bitrise/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/opt/android-sdk-linux/tools:/opt/android-sdk-linux/tools/bin:/opt/android-sdk-linux/platform-tools:/opt/android-ndk
    +++ swiftenv init -
    ++ eval 'export PATH="/root/.swiftenv/shims:${PATH}"
    command swiftenv rehash 2>/dev/null
    source '\''/root/.swiftenv/libexec/../libexec/../completions/swiftenv.bash'\'''
    +++ export PATH=/root/.swiftenv/shims:/root/.swiftenv/bin:/root/.bitrise/tools:/bitrise/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/opt/android-sdk-linux/tools:/opt/android-sdk-linux/tools/bin:/opt/android-sdk-linux/platform-tools:/opt/android-ndk
    +++ PATH=/root/.swiftenv/shims:/root/.swiftenv/bin:/root/.bitrise/tools:/bitrise/go/bin:/usr/local/sbin:/usr/local/bin:/usr/sbin:/usr/bin:/sbin:/bin:/usr/local/go/bin:/opt/android-sdk-linux/tools:/opt/android-sdk-linux/tools/bin:/opt/android-sdk-linux/platform-tools:/opt/android-ndk
    +++ command swiftenv rehash
    +++ source /root/.swiftenv/libexec/../libexec/../completions/swiftenv.bash
    ++++ complete -F _swiftenv swiftenv
    + AnyLint/.build/release/anylint
    Fatal error: Duplicate keys of type 'SearchOptions' were found in a Dictionary.
    This usually means either that the type violates Hashable's requirements, or
    that members of such a dictionary were mutated after insertion.
    Current stack trace:
    0    libswiftCore.so                    0x00007f3b7d08c490 swift_reportError + 50
    1    libswiftCore.so                    0x00007f3b7d0fdcd0 _swift_stdlib_reportFatalError + 69
    2    libswiftCore.so                    0x00007f3b7ce138ac <unavailable> + 1386668
    3    libswiftCore.so                    0x00007f3b7ce13c00 _assertionFailure(_:_:flags:) + 501
    4    libswiftCore.so                    0x00007f3b7cebf86c <unavailable> + 2091116
    5    libswiftCore.so                    0x00007f3b7cebd790 _NativeDictionary.resize(capacity:) + 1307
    6    libswiftCore.so                    0x00007f3b7ce54f10 _NativeDictionary.mutatingFind(_:isUnique:) + 453
    7    libswiftCore.so                    0x00007f3b7ce41760 Dictionary._Variant.setValue(_:forKey:) + 152
    8    libswiftCore.so                    0x00007f3b7d03e5ff <unavailable> + 3659263
    9    libswiftCore.so                    0x00007f3b7ce414c0 Dictionary.subscript.setter + 18
    10   lint                               0x000055bf470f28b3 <unavailable> + 108723
    11   lint                               0x000055bf470f3fa4 <unavailable> + 114596
    12   lint                               0x000055bf4711fa03 <unavailable> + 293379
    13   lint                               0x000055bf470f7c09 <unavailable> + 130057
    14   lint                               0x000055bf4711bf84 <unavailable> + 278404
    15   libc.so.6                          0x00007f3b7afa5740 __libc_start_main + 240
    16   lint                               0x000055bf470e3d89 <unavailable> + 48521
    14:52:43.221: ℹ️  Start linting using config file at /bitrise/src/lint.swift ...
    14:52:59.821: ❌ Linting failed using config file at /bitrise/src/lint.swift.
    
    bug 
    opened by Jeehut 1
  • Add option to output in Xcode compatible format

    Add option to output in Xcode compatible format

    Fixes #4.

    Unfortunately, it's currently only working on macOS targets due to an issue in Swift / swift-sh. See https://github.com/mxcl/swift-sh/issues/113 for more info.

    opened by Jeehut 1
  • Stop image from stretching on mobile

    Stop image from stretching on mobile

    A weird quirks of GitHub is that mobile pages override custom widths but not custom heights, causing stretching unless you specify width only.

    Before;

    D29B911B-B7C9-4275-A0B0-D21AA5E6E91F

    After:

    139501DD-13F2-4589-A09D-196BD0F1065E

    opened by ZevEisenberg 1
  • Make extension APIs public & More intelligent diffing

    Make extension APIs public & More intelligent diffing

    Fixes #27.

    Proposed Changes

    • Made internal extension methos public for usage in customCheck.
    • Print diff out to console for multiline autocorrections that were applied.
    opened by Jeehut 0
  • Add repeating option to checkFileContents

    Add repeating option to checkFileContents

    Problem Statement

    Sometimes a single regex isn't enough to catch and autocorrect all cases. For example, consider we want to add _ after each third number in long number literals, so we want to autocorrect 50000000000 to 50_000_000_000. While a pattern can be written like (\d+)(\d{3}) with a replacement of $1_$2 to fix the 3 last numbers, new checks need to be written to autocorrect other lengths.

    Suggested Solution

    Add an option to the checkFileContents method to repeat the check if autocorrections were applied until none are applied anymore. Could be named like repeatIfAutocorrected.

    Example Usage

    The exact example from the problem statement with repeatIfAutocorrected set to true.

    Possible Involvement

    • I could help with implementation: Yes.
    • I could help with testing: Yes.
    enhancement 
    opened by Jeehut 0
  • No violation when match equals to autoCorrectReplacement

    No violation when match equals to autoCorrectReplacement

    Fixes #26.

    Proposed Changes

    • Adds a new check to ensure running AnyLint as a pre-commit hook updates LinuxMain.swift before committing.
    • Updates README example for customCheck with real example (using above LinuxMain check).
    • Make sure that no violations are reported if a given autoCorrectReplacement leads to no changes, meaning the matched string is already correct. (See #26)
    opened by Jeehut 0
  • Automatically report diff for autocorrect replacement on longer texts

    Automatically report diff for autocorrect replacement on longer texts

    Problem Statement

    Currently, when doing changes in multi-line matches, the differences are not always clear. To make them more apparent, a diff could be used.

    enhancement 
    opened by Jeehut 0
  • Autocorrect-only checks

    Autocorrect-only checks

    Problem Statement

    Sometimes it is easier to create a regex that doesn't only match when there's an error, but it matches given structures to autocorrect them. For example, consider this check:

        // MARK: ObjCFunctionWhitespace
        try Lint.checkFileContents(
            checkInfo: "ObjCFunctionWhitespace@info: Whitespaces on ObjC functions should be of structure: `- (type)function:(Type *)param {`",
            regex: #"^([\+\-]) *\( *([^)]+) *\) *([^\n\{]+[^ \n])\s*\{\s*\n( *\S+)"#,
            matchingExamples: ["- (void)viewDidLoad\n{\n    blubb", "-(void)viewDidLoad {\n    blubb"],
            includeFilters: [objectiveCFiles],
            autoCorrectReplacement: "$1 ($2)$3 {\n$4",
            autoCorrectExamples: [
                ["before": "- (void)viewDidLoad\n{\n    blubb", "after": "- (void)viewDidLoad {\n    blubb"],
                ["before": "-(void)viewDidLoad {\n    blubb", "after": "- (void)viewDidLoad {\n    blubb"],
            ]
        )
    

    This was set to level info to not report any warnings or errors, but in some cases it does have warnings or errors, in others it doesn't. This could be auto-detected by checking if the replacement of the autocorrection is actually the same or not, then there would be no need for @info level and no violations such as this would be reported:

    17:22:01.956: ℹ️  > 1140. App/Sources/AppDelegate.m:1574:1:
    17:22:01.956: ℹ️          Autocorrection applied (before >>> after):
    17:22:01.956: ℹ️          > ✗ -␣(void)applicationWillResignActive:(UIApplication␣*)application␣{\n␣␣␣␣//
    17:22:01.956: ℹ️          >>>
    17:22:01.956: ℹ️          > ✓ -␣(void)applicationWillResignActive:(UIApplication␣*)application␣{\n␣␣␣␣//
    

    Suggested Solution

    If there is an autoCorrectReplacement specified on a rule, we should remove the violation from the results as there seems to be no violation. This enables some easier regex specifications and doesn't require to split them up e.g. when they match from beginning or end. The regex is then not only used for violations, but more for the entire structure that should be autocorrected. It is something like an autocorrect-only check.

    Example Usage

    See above.

    Possible Involvement

    • I could help with implementation: Yes.
    • I could help with testing: Sure.
    enhancement 
    opened by Jeehut 0
  • Add cache for same search options in file search

    Add cache for same search options in file search

    Fixes #20.

    I investigated and we already skip all subdirectories when any of the excludeFilters applies or it's a hidden directory. The includeFilters shortcut optimization is hard to implement, as the combination of all includeFilters is used per check and they might differ. Instead, I opted for caching if the file search combination is the same.

    opened by Jeehut 0
  • Test out & document usage on GitHub CI

    Test out & document usage on GitHub CI

    Problem Statement

    Currently, installing and running AnyLint on the CI takes too much time.

    Suggested Solution

    This step could potentially be officially documented as a way to set AnyLint up on CI: https://github.com/Cyberbeni/install-swift-tool

    enhancement 
    opened by Jeehut 0
  • Report performance of each custom rule to highlight potential issues

    Report performance of each custom rule to highlight potential issues

    Problem Statement

    Currently, when there's a Regex or Swift custom rule defined that takes significantly longer to execute its checks, users have to wait without knowing why.

    Suggested Solution

    Report performance of every custom rule somewhere and highlight outliers specifically to inform users about slow ones.

    enhancement 
    opened by Jeehut 0
  • Improve performance on subsequent runs by precompiling executable

    Improve performance on subsequent runs by precompiling executable

    Problem Statement

    Currently, when running the Swift script even with no changes to the Script file, the whole Swift compiler is started fresh leading to the script taking a few seconds and letting one wait longer than needed.

    Suggested Solution

    Instead of simply running the Script file via swift-sh, wrap it in a command line container and build an executable file. Then reuse that executable file as long as the Script file contents are identical, e.g. by saving its SHA hash. This is like a caching mechanism.

    enhancement 
    opened by Jeehut 0
  • `AnyLint.skipHere: RuleName` does not work for custom checks

    `AnyLint.skipHere: RuleName` does not work for custom checks

    Expected Behavior

    Whenever I specify the comment // AnyLint.skipHere: RuleName somewhere, I expect the rule violations to be ignored.

    Actual Behavior

    When using a customCheck custom Swift code to create a violation, the ignore does not work.

    bug 
    opened by Jeehut 0
  • Add option to run without autocorrection

    Add option to run without autocorrection

    Problem Statement

    Currently, when auto-correction is provided for a rule, it's always running it, even when run via build-script in Xcode.

    Suggested Solution

    Provide an option to lint only, without auto-correcting, e.g. --no-autocorrect / -n.

    enhancement 
    opened by Jeehut 0
Releases(0.10.1)
  • 0.10.1(May 27, 2022)

    Update Notice: After upgrading AnyLint via brew upgrade anylint you should run swift-sh --clean-cache to ensure the AnyLint library in your script gets the latest update, too. Alternatively, specify the version explicitly in your lint.swift file via import AnyLint // [email protected] (note the @0.10.1 suffix).

    Changed

    • Improved output color & formatting of new --measure option for printing execution time per check.
      Author: Cihat Gündüz

    Fixed

    • New --measure option did not work when no violations were found, now also prints when all checks succeed.
      Author: Cihat Gündüz
    Source code(tar.gz)
    Source code(zip)
  • 0.10.0(May 27, 2022)

    Update Notice: After upgrading AnyLint via brew upgrade anylint you should run swift-sh --clean-cache to ensure the AnyLint library in your script gets the latest update, too. Alternatively, specify the version explicitly in your lint.swift file via import AnyLint // [email protected] (note the @0.10.1 which isn't added by default).

    Added

    • New --measure / -m option to print execution times per check to find slow checks easily.
      Author: Cihat Gündüz

    Changed

    • The execution time of all checks are now being measured, independent of what options are provided.
      Author: Cihat Gündüz
    Source code(tar.gz)
    Source code(zip)
  • 0.9.2(Apr 25, 2022)

  • 0.9.1(Apr 25, 2022)

  • 0.9.0(Apr 24, 2022)

    Added

    • Added new option violationLocation parameter for checkFileContents for specifying position of violation marker using .init(range:bound:), where range can be one of .fullMatch or .captureGroup(index:) and bound one of .lower or .upper.
    Source code(tar.gz)
    Source code(zip)
  • 0.8.5(Apr 24, 2022)

  • 0.8.4(Apr 1, 2022)

  • 0.8.3(Oct 13, 2021)

  • 0.8.2(Jun 9, 2020)

  • 0.8.1(Jun 8, 2020)

  • 0.8.0(May 18, 2020)

  • 0.7.0(May 17, 2020)

    Added

    • A new AnyLint custom check was added to ensure AnyLint fails when LinuxMain.swift isn't up-to-date, useful as a git pre-commit hook.
      Author: Cihat Gündüz | PR: #28

    Changed

    • When a given autoCorrectReplacement on the checkFileContents method leads to no changes, the matched string of the given regex is considered to be already correct, thus no violation is reported anymore.
      Issue: #26 | PR: #28 | Author: Cihat Gündüz
    • A CI pipeline using GitHub Actions was setup, which is much faster as it runs multiple tasks in parallel than Bitrise.
      Author: Cihat Gündüz
    Source code(tar.gz)
    Source code(zip)
  • 0.6.3(May 7, 2020)

  • 0.6.2(May 7, 2020)

  • 0.6.1(Apr 25, 2020)

    Changed

    • Hugely improved performance of subsequent file searches with the same combination of includeFilters and excludeFilters. For example, if 30 checks were sharing the same filters, each file search is now ~8x faster.
      Issue: #20 | PR: #21 | Author: Cihat Gündüz
    Source code(tar.gz)
    Source code(zip)
  • 0.6.0(Apr 23, 2020)

  • 0.5.0(Apr 22, 2020)

  • 0.4.0(Apr 20, 2020)

  • 0.3.0(Apr 17, 2020)

  • 0.2.0(Apr 10, 2020)

    Added

    • Added new -x / --xcode option to print out warnings & errors in an Xcode-compatible manner to improve user experience when used with an Xcode build script. Requires arguments: CommandLine.arguments as parameters to logSummary in config file.
      Issue: #4 | PR: #8 | Author: Cihat Gündüz
    Source code(tar.gz)
    Source code(zip)
  • 0.1.1(Mar 23, 2020)

  • 0.1.0(Mar 22, 2020)

Owner
Flinesoft
Flinesoft
A tool for Swift code modification intermediating between code generation and formatting.

swift-mod A tool for Swift code modification intermediating between code generation and formatting. Overview swift-mod is a tool for Swift code modifi

Ryo Aoyama 95 Nov 3, 2022
SwiftCop is a validation library fully written in Swift and inspired by the clarity of Ruby On Rails Active Record validations.

SwiftCop is a validation library fully written in Swift and inspired by the clarity of Ruby On Rails Active Record validations. Objective Build a stan

Andres Canal 542 Sep 17, 2022
A command-line tool and Xcode Extension for formatting Swift code

Table of Contents What? Why? How? Command-line tool Xcode source editor extension Xcode build phase Via Applescript VSCode plugin Sublime Text plugin

Nick Lockwood 6.3k Jan 8, 2023
A tool to enforce Swift style and conventions.

SwiftLint A tool to enforce Swift style and conventions, loosely based on the now archived GitHub Swift Style Guide. SwiftLint enforces the style guid

Realm 16.9k Jan 9, 2023
An Xcode formatter plug-in to format your swift code.

Swimat Swimat is an Xcode plug-in to format your Swift code. Preview Installation There are three way to install. Install via homebrew-cask # Homebrew

Jintin 1.6k Jan 7, 2023
Cross-platform static analyzer and linter for Swift.

Wiki • Installation • Usage • Features • Developers • License Tailor is a cross-platform static analysis and lint tool for source code written in Appl

Sleekbyte 1.4k Dec 19, 2022
Type-safe observable values and collections in Swift

GlueKit ⚠️ WARNING ⚠️ This project is in a prerelease state. There is active work going on that will result in API changes that can/will break code wh

null 361 Oct 12, 2022
💊 Syntactic sugar for Swift do-try-catch

Fallback Syntactic sugar for Swift do-try-catch. At a Glance value = try fallback( try get("A"), try get("B"), try get("C"), try get("D") ) is

Suyeol Jeon 43 May 25, 2020
A Swift micro-framework to easily deal with weak references to self inside closures

WeakableSelf Context Closures are one of Swift must-have features, and Swift developers are aware of how tricky they can be when they capture the refe

Vincent Pradeilles 72 Sep 1, 2022
Measure Swift code metrics and get reports in Xcode, Jenkins and other CI platforms.

Taylor ⚠️ Taylor is DEPRECATED. Use SwiftLint instead. A tool aimed to increase Swift code quality, by checking for conformance to code metrics. Taylo

YOPESO 301 Dec 24, 2022
Aplicação Basica em Swift desenvolvida com o intuito de aplicar os conceitos estudados

Via Cep iOS Sobre - Interface do Usuario - Tecnologias - Requisitos - Autor Projeto ?? FINALIZADO ?? Sobre A Aplicação consiste em fazer buscas usando

Igor Damasceno de Sousa 1 Jun 3, 2022
JSONHelper - ✌ Convert anything into anything in one operation; JSON data into class instances, hex strings into UIColor/NSColor, y/n strings to booleans, arrays and dictionaries of these; anything you can make sense of!

JSONHelper Convert anything into anything in one operation; hex strings into UIColor/NSColor, JSON strings into class instances, y/n strings to boolea

Baris Sencan 788 Jul 19, 2022
Regular expressions for swift

Regex Advanced regular expressions for Swift Goals Regex library was mainly introduced to fulfill the needs of Swift Express - web application server

Crossroad Labs 328 Nov 20, 2022
SwiftVerbalExpressions is a Swift library that helps to construct difficult regular expressions

SwiftVerbalExpressions Swift Regular Expressions made easy SwiftVerbalExpressions is a Swift library that helps to construct difficult regular express

null 582 Jun 29, 2022
Regular expressions for swift

Regex Advanced regular expressions for Swift Goals Regex library was mainly introduced to fulfill the needs of Swift Express - web application server

Crossroad Labs 328 Nov 20, 2022
Swifty regular expressions

Regex Swifty regular expressions This is a wrapper for NSRegularExpression that makes it more convenient and type-safe to use regular expressions in S

Sindre Sorhus 311 Nov 22, 2022
Power! Unlimited power for ARKit 2.0!

A long time ago in a galaxy, far, far away... It is a period when iPhone SE and iPhone X were destroyed from the apple store, the AR market was under

KBOY (Kei Fujikawa) 516 Dec 1, 2022
Demo Swift Lint Two Warnings

DemoSwiftLintTwoWarnings Supports: iOS 10.0 and above Branches: master - stable app releases develop - development branch, merge your feature branches

Igor Makarov 0 Nov 25, 2021
A simple cache that can hold anything, including Swift items

CacheIsKing CacheIsKing is a simple cache that allows you to store any item, including objects, pure Swift structs, enums (with associated values), et

Christopher Luu 13 Jan 22, 2018
Swift-lint-plugin - A SwiftPM plugin that adds a linting command

SwiftLintPlugin This is a SwiftPM plugin that adds a lint command. SwiftPM plugi

null 9 Nov 23, 2022