{"id":28437079,"url":"https://github.com/flinedev/anylint","last_synced_at":"2025-06-27T20:32:10.996Z","repository":{"id":48980926,"uuid":"245607272","full_name":"FlineDev/AnyLint","owner":"FlineDev","description":"Lint anything by combining the power of scripts \u0026 regular expressions.","archived":false,"fork":false,"pushed_at":"2023-04-09T22:14:02.000Z","size":912,"stargazers_count":117,"open_issues_count":21,"forks_count":4,"subscribers_count":1,"default_branch":"main","last_synced_at":"2025-06-05T23:08:38.485Z","etag":null,"topics":["autocorrection","lint-check","regex","scripts","swift"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/FlineDev.png","metadata":{"files":{"readme":"README.md","changelog":"CHANGELOG.md","contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null,"governance":null,"roadmap":null,"authors":null}},"created_at":"2020-03-07T09:53:32.000Z","updated_at":"2025-04-25T12:54:51.000Z","dependencies_parsed_at":"2024-01-02T21:03:51.965Z","dependency_job_id":null,"html_url":"https://github.com/FlineDev/AnyLint","commit_stats":{"total_commits":186,"total_committers":3,"mean_commits":62.0,"dds":"0.021505376344086002","last_synced_commit":"6c2b94073d4faa38554f8e1923161e01ac211608"},"previous_names":["flinesoft/anylint"],"tags_count":23,"template":false,"template_full_name":null,"purl":"pkg:github/FlineDev/AnyLint","repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlineDev%2FAnyLint","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlineDev%2FAnyLint/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlineDev%2FAnyLint/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlineDev%2FAnyLint/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/FlineDev","download_url":"https://codeload.github.com/FlineDev/AnyLint/tar.gz/refs/heads/main","sbom_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/FlineDev%2FAnyLint/sbom","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":262327311,"owners_count":23294250,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["autocorrection","lint-check","regex","scripts","swift"],"created_at":"2025-06-05T23:08:37.838Z","updated_at":"2025-06-27T20:32:10.939Z","avatar_url":"https://github.com/FlineDev.png","language":"Swift","readme":"\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/FlineDev/AnyLint/main/Logo.png\"\n      width=562 /\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n    \u003ca href=\"https://github.com/FlineDev/AnyLint/actions?query=branch%3Amain\"\u003e\n        \u003cimg src=\"https://github.com/FlineDev/AnyLint/workflows/CI/badge.svg\"\n            alt=\"CI\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.codacy.com/gh/FlineDev/AnyLint\"\u003e\n        \u003cimg src=\"https://api.codacy.com/project/badge/Grade/c881ee12938145d3bfd398eff1571228\"\n             alt=\"Code Quality\"/\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://www.codacy.com/gh/FlineDev/AnyLint\"\u003e\n        \u003cimg src=\"https://api.codacy.com/project/badge/Coverage/c881ee12938145d3bfd398eff1571228\"\n             alt=\"Coverage\"/\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/FlineDev/AnyLint/releases\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/Version-0.11.0-blue.svg\"\n             alt=\"Version: 0.11.0\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/FlineDev/AnyLint/blob/main/LICENSE\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/License-MIT-lightgrey.svg\"\n             alt=\"License: MIT\"\u003e\n    \u003c/a\u003e\n    \u003cbr /\u003e\n    \u003ca href=\"https://paypal.me/Dschee/5EUR\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/PayPal-Donate-orange.svg\"\n             alt=\"PayPal: Donate\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://github.com/sponsors/Jeehut\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/GitHub-Become a sponsor-orange.svg\"\n             alt=\"GitHub: Become a sponsor\"\u003e\n    \u003c/a\u003e\n    \u003ca href=\"https://patreon.com/Jeehut\"\u003e\n        \u003cimg src=\"https://img.shields.io/badge/Patreon-Become a patron-orange.svg\"\n             alt=\"Patreon: Become a patron\"\u003e\n    \u003c/a\u003e\n\u003c/p\u003e\n\n\u003cp align=\"center\"\u003e\n  \u003ca href=\"#installation\"\u003eInstallation\u003c/a\u003e\n  • \u003ca href=\"#getting-started\"\u003eGetting Started\u003c/a\u003e\n  • \u003ca href=\"#configuration\"\u003eConfiguration\u003c/a\u003e\n  • \u003ca href=\"#xcode-build-script\"\u003eXcode Build Script\u003c/a\u003e\n  • \u003ca href=\"#donation\"\u003eDonation\u003c/a\u003e\n  • \u003ca href=\"https://github.com/FlineDev/AnyLint/issues\"\u003eIssues\u003c/a\u003e\n  • \u003ca href=\"#regex-cheat-sheet\"\u003eRegex Cheat Sheet\u003c/a\u003e\n  • \u003ca href=\"#license\"\u003eLicense\u003c/a\u003e\n\u003c/p\u003e\n\n# AnyLint\n\nLint any project in any language using Swift and regular expressions. With built-in support for matching and non-matching examples validation \u0026 autocorrect replacement. Replaces SwiftLint custom rules \u0026 works for other languages as well! 🎉\n\n## Installation\n\n### Via [Homebrew](https://brew.sh):\n\nTo **install** AnyLint the first time, run these commands:\n\n```bash\nbrew tap FlineDev/AnyLint https://github.com/FlineDev/AnyLint.git\nbrew install anylint\n```\n\nTo **update** it to the latest version, run this instead:\n\n```bash\nbrew upgrade anylint\n```\n\n### Via [Mint](https://github.com/yonaskolb/Mint):\n\nTo **install** AnyLint or **update** to the latest version, run this command:\n\n```bash\nmint install FlineDev/AnyLint\n```\n\n## Getting Started\n\nTo initialize AnyLint in a project, run:\n\n```bash\nanylint --init blank\n```\n\nThis will create the Swift script file `lint.swift` with something like the following contents:\n\n```swift\n#!/opt/local/bin/swift-sh\nimport AnyLint // @FlineDev\n\nLint.logSummaryAndExit(arguments: CommandLine.arguments) {\n    // MARK: - Variables\n    let readmeFile: Regex = #\"README\\.md\"#\n\n    // MARK: - Checks\n    // MARK: Readme\n    try Lint.checkFilePaths(\n        checkInfo: \"Readme: Each project should have a README.md file explaining the project.\",\n        regex: readmeFile,\n        matchingExamples: [\"README.md\"],\n        nonMatchingExamples: [\"README.markdown\", \"Readme.md\", \"ReadMe.md\"],\n        violateIfNoMatchesFound: true\n    )\n\n    // MARK: ReadmeTypoLicense\n    try Lint.checkFileContents(\n        checkInfo: \"ReadmeTypoLicense: Misspelled word 'license'.\",\n        regex: #\"([\\s#]L|l)isence([\\s\\.,:;])\"#,\n        matchingExamples: [\" license:\", \"## Lisence\\n\"],\n        nonMatchingExamples: [\" license:\", \"## License\\n\"],\n        includeFilters: [readmeFile],\n        autoCorrectReplacement: \"$1icense$2\",\n        autoCorrectExamples: [\n            [\"before\": \" lisence:\", \"after\": \" license:\"],\n            [\"before\": \"## Lisence\\n\", \"after\": \"## License\\n\"],\n        ]\n    )\n}\n\n```\n\nThe most important thing to note is that the **first three lines are required** for AnyLint to work properly.\n\nAll 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](https://github.com/mxcl/swift-sh). Thus, you can run any Swift code and even import Swift packages (see the [swift-sh docs](https://github.com/mxcl/swift-sh#usage)) 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.\n\nHaving 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](#configuration) section below.\n\nIf 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:\n\nInitializes the configuration files at the given locations:\n```bash\nanylint --init blank --path Sources/lint.swift --path Tests/lint.swift\n```\n\nRuns the lint checks for both configuration files:\n```bash\nanylint --path Sources/lint.swift --path Tests/lint.swift\n```\n\nThere are also several flags you can pass to `anylint`:\n\n1. `-s` / `--strict`: Fails on warnings as well. (By default, the command only fails on errors.)\n1. `-x` / `--xcode`: Prints warnings \u0026 errors in a format to be reported right within Xcodes left sidebar.\n1. `-l` / `--validate`: Runs only validations for `matchingExamples`, `nonMatchingExamples` and `autoCorrectExamples`.\n1. `-u` / `--unvalidated`: Runs the checks without validating their correctness. Only use for faster subsequent runs after a validated run succeeded.\n1. `-m` / `--measure`: Prints the time it took to execute each check for performance optimizations.\n1. `-v` / `--version`: Prints the current tool version. (Does not run any lint checks.)\n1. `-d` / `--debug`: Logs much more detailed information about what AnyLint is doing for debugging purposes.\n\n## Configuration\n\nAnyLint provides three different kinds of lint checks:\n\n1. `checkFileContents`: Matches the contents of a text file to a given regex.\n2. `checkFilePaths`: Matches the file paths of the current directory to a given regex.\n3. `customCheck`: Allows to write custom Swift code to do other kinds of checks.\n\nSeveral examples of lint checks can be found in the [`lint.swift` file of this very project](https://github.com/FlineDev/AnyLint/blob/main/lint.swift).\n\n### Basic Types\n\nIndependent from the method used, there are a few types specified in the AnyLint package you should know of.\n\n#### Regex\n\nMany parameters in the above mentioned lint check methods are of `Regex` type. A `Regex` can be initialized in several ways:\n\n1. Using a **String**:\n  ```swift\n  let regex = Regex(#\"(foo|bar)[0-9]+\"#) // =\u003e /(foo|bar)[0-9]+/\n  let regexWithOptions = Regex(#\"(foo|bar)[0-9]+\"#, options: [.ignoreCase, .dotMatchesLineSeparators, .anchorsMatchLines]) // =\u003e /(foo|bar)[0-9]+/im\n  ```\n2. Using a **String Literal**:\n  ```swift\n  let regex: Regex = #\"(foo|bar)[0-9]+\"#  // =\u003e /(foo|bar)[0-9]+/\n  let regexWithOptions: Regex = #\"(foo|bar)[0-9]+\\im\"#  // =\u003e /(foo|bar)[0-9]+/im\n  ```\n3. Using a **Dictionary Literal**: (use for [named capture groups](https://www.regular-expressions.info/named.html))\n  ```swift\n  let regex: Regex = [\"key\": #\"foo|bar\"#, \"num\": \"[0-9]+\"] // =\u003e /(?\u003ckey\u003efoo|bar)(?\u003cnum\u003e[0-9]+)/\n  let regexWithOptions: Regex = [\"key\": #\"foo|bar\"#, \"num\": \"[0-9]+\", #\"\\\"#: \"im\"] // =\u003e /(?\u003ckey\u003efoo|bar)(?\u003cnum\u003e[0-9]+)/im\n  ```\n\nNote that we recommend using [raw strings](https://www.hackingwithswift.com/articles/162/how-to-use-raw-strings-in-swift) (`#\"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](https://rubular.com/) first and then copy \u0026 pasting from them without any additional escaping (except for `{` \u0026 `}`, replace with `\\{` \u0026 `\\}`).\n\n\u003cdetails\u003e\n\u003csummary\u003eRegex Options\u003c/summary\u003e\n\nSpecifying Regex options in literals is done via the `\\` separator as shown in the examples above. The available options are:\n\n1.  `i` for `.ignoreCase`: Any specified characters will both match uppercase and lowercase variants.\n2. `m` for `.dotMatchesLineSeparators`: All appearances of `.` in regexes will also match newlines (which are not matched against by default).\n\nThe `.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 \u0026 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](https://rubular.com/).\n\n\u003c/details\u003e\n\n#### CheckInfo\n\nA `CheckInfo` contains the basic information about a lint check. It consists of:\n\n1. `id`: The identifier of your lint check. For example: `EmptyTodo`\n2. `hint`: The hint explaining the cause of the violation or the steps to fix it.\n3. `severity`: The severity of violations. One of `error`, `warning`, `info`. Default: `error`\n\nWhile there is an initializer available, we recommend using a String Literal instead like so:\n\n```swift\n// accepted structure: \u003cid\u003e(@\u003cseverity\u003e): \u003chint\u003e\nlet checkInfo: CheckInfo = \"ReadmePath: The README file should be named exactly `README.md`.\"\nlet checkInfoCustomSeverity: CheckInfo = \"ReadmePath@warning: The README file should be named exactly `README.md`.\"\n```\n\n#### AutoCorrection\n\nAn `AutoCorrection` contains an example `before` and `after` string to validate that a given autocorrection rule behaves correctly.\n\nIt can be initialized in two ways, either with the default initializer:\n\n```swift\nlet example: AutoCorrection = AutoCorrection(before: \"Lisence\", after: \"License\")\n```\n\nOr using a Dictionary literal:\n\n```swift\nlet example: AutoCorrection = [\"before\": \"Lisence\", \"after\": \"License\"]\n```\n\n### Check File Contents\n\nAnyLint 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.\n\nIn its simplest form, the method just requires a `checkInfo` and a `regex`:\n\n```swift\n// MARK: EmptyTodo\ntry Lint.checkFileContents(\n    checkInfo: \"EmptyTodo: TODO comments should not be empty.\",\n    regex: #\"// TODO: *\\n\"#\n)\n```\n\nBut we *strongly recommend* to always provide also:\n\n1. `matchingExamples`: Array of strings expected to match the given string for `regex` validation.\n2. `nonMatchingExamples`: Array of strings not matching the given string for `regex` validation.\n3. `includeFilters`: Array of `Regex` objects to include to the file paths to check.\n\nThe 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.\n\nThe 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.\n\nHere's the *recommended minimum example*:\n\n```swift\n// MARK: - Variables\nlet swiftSourceFiles: Regex = #\"Sources/.*\\.swift\"#\nlet swiftTestFiles: Regex = #\"Tests/.*\\.swift\"#\n\n// MARK: - Checks\n// MARK: empty_todo\ntry Lint.checkFileContents(\n    checkInfo: \"EmptyTodo: TODO comments should not be empty.\",\n    regex: #\"// TODO: *\\n\"#,\n    matchingExamples: [\"// TODO:\\n\"],\n    nonMatchingExamples: [\"// TODO: not yet implemented\\n\"],\n    includeFilters: [swiftSourceFiles, swiftTestFiles]\n)\n```\n\nThere's 5 more parameters you can optionally set if needed:\n\n1. `excludeFilters`: Array of `Regex` objects to exclude from the file paths to check.\n1. `violationLocation`: Specifies the position of the violation marker violations should be reported. Can be the `lower` or `upper` end of a `fullMatch` or `captureGroup(index:)`.  \n1. `autoCorrectReplacement`: Replacement string which can reference any capture groups in the `regex`.\n1. `autoCorrectExamples`: Example structs with `before` and `after` for autocorrection validation.\n1. `repeatIfAutoCorrected`: Repeat check if at least one auto-correction was applied in last run. Defaults to `false`.\n\nThe `excludeFilters` can be used alternatively to the `includeFilters` or alongside them. If used alongside, exclusion will take precedence over inclusion.\n\nIf `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+)` =\u003e `$1$2`) and named group style (`(?\u003calpha\u003e[a-z])(?\u003cnum\u003e\\d+)` =\u003e `$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.\n\n\u003e *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.\n\nHere's a *full example using all parameters* at once:\n\n```swift\n// MARK: - Variables\nlet swiftSourceFiles: Regex = #\"Sources/.*\\.swift\"#\nlet swiftTestFiles: Regex = #\"Tests/.*\\.swift\"#\n\n// MARK: - Checks\n// MARK: empty_method_body\ntry Lint.checkFileContents(\n    checkInfo: \"EmptyMethodBody: Don't use whitespaces for the body of empty methods.\",\n    regex: [\n      \"declaration\": #\"func [^\\(\\s]+\\([^{]*\\)\"#,\n      \"spacing\": #\"\\s*\"#,\n      \"body\": #\"\\{\\s+\\}\"#\n    ],\n    violationLocation: .init(range: .fullMatch, bound: .upper),\n    matchingExamples: [\n        \"func foo2bar()  { }\",\n        \"func foo2bar(x: Int, y: Int)  { }\",\n        \"func foo2bar(\\n    x: Int,\\n    y: Int\\n) {\\n    \\n}\",\n    ],\n    nonMatchingExamples: [\n      \"func foo2bar() {}\",\n      \"func foo2bar(x: Int, y: Int) {}\"\n    ],\n    includeFilters: [swiftSourceFiles],\n    excludeFilters: [swiftTestFiles],\n    autoCorrectReplacement: \"$declaration {}\",\n    autoCorrectExamples: [\n        [\"before\": \"func foo2bar()  { }\", \"after\": \"func foo2bar() {}\"],\n        [\"before\": \"func foo2bar(x: Int, y: Int)  { }\", \"after\": \"func foo2bar(x: Int, y: Int) {}\"],\n        [\"before\": \"func foo2bar()\\n{\\n    \\n}\", \"after\": \"func foo2bar() {}\"],\n    ]\n)\n```\n\nNote 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:\n\n```Java\nif(x == 5) { /* some code */ }\nif (x == 5){ /* some code */ }\nif(x == 5){ /* some code */ }\nif (x == 5) { /* some code */ }\n```\n\nThe 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. 🎉\n\n(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.)\n\n#### Skip file content checks\n\nWhile 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**.\n\nFor such cases, there are **2 ways to skip checks** within the files themselves:\n\n1. `AnyLint.skipHere: \u003cCheckInfo.ID\u003e`: Will skip the specified check(s) on the same line and the next line.\n\n  ```swift\n  var x: Int = 5 // AnyLint.skipHere: MinVarNameLength\n\n  // or\n\n  // AnyLint.skipHere: MinVarNameLength\n  var x: Int = 5\n  ```\n\n2. `AnyLint.skipInFile: \u003cAll or CheckInfo.ID\u003e`: Will skip `All` or specificed check(s) in the entire file.\n\n  ```swift\n  // AnyLint.skipInFile: MinVarNameLength\n\n  var x: Int = 5\n  var y: Int = 5\n  ```\n  or\n\n  ```swift\n  // AnyLint.skipInFile: All\n\n  var x: Int = 5\n  var y: Int = 5\n  ```\n\nIt is also possible to skip multiple checks at once in a line like so:\n\n```swift\n// AnyLint.skipHere: MinVarNameLength, LineLength, ColonWhitespaces\n```\n\n### Check File Paths\n\nThe `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:\n\n1. `autoCorrectReplacement`: Here, this will safely move the file using the path replacement.\n2. `violateIfNoMatchesFound`: Will report a violation if _no_ matches are found if `true`. Default: `false`\n\nAs 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.\n\nBy 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.\n\n### Custom Checks\n\nAnyLint 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](https://swiftpm.co/). 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:\n\n1. `checkInfo`: Provides some general information on the lint check.\n2. `customClosure`: Your custom logic which produces an array of `Violation` objects.\n\nNote 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.\n\nIf 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](https://github.com/FlineDev/HandySwift#regex) (the project, the class was taken from) or read the [code documentation comments](https://github.com/FlineDev/AnyLint/blob/main/Sources/Utility/Regex.swift).\n\nWhen using the `customCheck`, you might want to also include some Swift packages for [easier file handling](https://github.com/JohnSundell/Files) or [running shell commands](https://github.com/JohnSundell/ShellOut). You can do so by adding them at the top of the file like so:\n\n```swift\n#!/opt/local/bin/swift-sh\nimport AnyLint // @FlineDev\nimport ShellOut // @JohnSundell\n\nLint.logSummaryAndExit(arguments: CommandLine.arguments) {\n    // MARK: - Variables\n    let projectName: String = \"AnyLint\"\n\n    // MARK: - Checks\n    // MARK: LinuxMainUpToDate\n    try Lint.customCheck(checkInfo: \"LinuxMainUpToDate: The tests in Tests/LinuxMain.swift should be up-to-date.\") { checkInfo in\n        var violations: [Violation] = []\n\n        let linuxMainFilePath = \"Tests/LinuxMain.swift\"\n        let linuxMainContentsBeforeRegeneration = try! String(contentsOfFile: linuxMainFilePath)\n\n        let sourceryDirPath = \".sourcery\"\n        try! shellOut(to: \"sourcery\", arguments: [\"--sources\", \"Tests/\\(projectName)Tests\", \"--templates\", \"\\(sourceryDirPath)/LinuxMain.stencil\", \"--output\", sourceryDirPath])\n\n        let generatedLinuxMainFilePath = \"\\(sourceryDirPath)/LinuxMain.generated.swift\"\n        let linuxMainContentsAfterRegeneration = try! String(contentsOfFile: generatedLinuxMainFilePath)\n\n        // move generated file to LinuxMain path to update its contents\n        try! shellOut(to: \"mv\", arguments: [generatedLinuxMainFilePath, linuxMainFilePath])\n\n        if linuxMainContentsBeforeRegeneration != linuxMainContentsAfterRegeneration {\n            violations.append(\n                Violation(\n                    checkInfo: checkInfo,\n                    filePath: linuxMainFilePath,\n                    appliedAutoCorrection: AutoCorrection(\n                        before: linuxMainContentsBeforeRegeneration,\n                        after: linuxMainContentsAfterRegeneration\n                    )\n                )\n            )\n        }\n\n        return violations\n    }\n}\n\n```\n\n## Xcode Build Script\n\nIf 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:\n\n```bash\nexport PATH=\"$PATH:/opt/homebrew/bin\"\n\nif which anylint \u003e /dev/null; then\n    anylint -x\nelse\n    echo \"warning: AnyLint not installed, see from https://github.com/FlineDev/AnyLint\"\nfi\n```\n\nNext, make sure the AnyLint script runs before the steps `Compiling Sources` by moving it per drag \u0026 drop, for example right after `Dependencies`. You probably also want to rename it to somethng like `AnyLint`.\n\n\u003e **_Note_**: There's a [known bug](https://github.com/mxcl/swift-sh/issues/113) when the build script is used in non-macOS platforms targets.\n\n## Regex Cheat Sheet\n\nRefer to the Regex quick reference on [rubular.com](https://rubular.com/) which all apply for Swift as well:\n\u003cp align=\"center\"\u003e\n    \u003cimg src=\"https://raw.githubusercontent.com/FlineDev/AnyLint/main/RubularQuickReference.png\"\n      width=900 /\u003e\n\u003c/p\u003e\n\nIn Swift, there are some **differences to regexes in Ruby** (which rubular.com is based on) – take care when copying regexes:\n\n1. In Ruby, forward slashes (`/`) must be escaped (`\\/`), that's not necessary in Swift.\n2. In Swift, curly braces (`{` \u0026 `}`) must be escaped (`\\{` \u0026 `\\}`), that's not necessary in Ruby.\n\nHere are some **advanced Regex features** you might want to use or learn more about:\n\n1. Back references can be used within regexes to match previous capture groups.\n\n   For example, you can make sure that the PR number and link match in `PR: [#100](https://github.com/FlineDev/AnyLint/pull/100)` by using a capture group (`(\\d+)`) and a back reference (`\\1`) like in: `\\[#(\\d+)\\]\\(https://[^)]+/pull/\\1\\)`.\n\n   [Learn more](https://www.regular-expressions.info/backref.html)\n\n2. Negative \u0026 positive lookaheads \u0026 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), `(?\u003c=PATTERN)` (positive lookbehind) or `(?\u003c!PATTERN)` (negative lookbehind).\n\n   For example, you could use the regex `- (?!None\\.).*` to match any entry in a `CHANGELOG.md` file except empty ones called `None.`.\n\n   [Learn more](https://www.regular-expressions.info/lookaround.html)\n\n3. 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.\n\n   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: `(?\u003c={\\n)\\s*\\n\\s*\\S`.\n\n   See also [#3](https://github.com/FlineDev/AnyLint/issues/3)\n\n## Donation\n\nAnyLint was brought to you by [Cihat Gündüz](https://github.com/Jeehut) in his free time. If you want to thank me and support the development of this project, please **make a small donation on [PayPal](https://paypal.me/Dschee/5EUR)**. In case you also like my other [open source contributions](https://github.com/FlineDev) and [articles](https://medium.com/@Jeehut), please consider motivating me by **becoming a sponsor on [GitHub](https://github.com/sponsors/Jeehut)** or a **patron on [Patreon](https://www.patreon.com/Jeehut)**.\n\nThank you very much for any donation, it really helps out a lot! 💯\n\n## Contributing\n\nContributions 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](http://chris.beams.io/posts/git-commit/)). Also, please make sure to add an entry to the `CHANGELOG.md` file which explains your change.\n\n## License\n\nThis library is released under the [MIT License](http://opensource.org/licenses/MIT). See LICENSE for details.\n","funding_links":["https://paypal.me/Dschee/5EUR","https://github.com/sponsors/Jeehut","https://patreon.com/Jeehut","https://paypal.me/Dschee/5EUR)*","https://www.patreon.com/Jeehut)*"],"categories":[],"sub_categories":[],"project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflinedev%2Fanylint","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fflinedev%2Fanylint","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fflinedev%2Fanylint/lists"}