SwiftLint is the best tool to enforce coding standards and best practices. It comes with pre-defined rules that can be customized to suit individual project needs. However, sometimes the built-in rules fall short of project requirements. In such cases, we write regular expressions to fill the gap.
While regex is a powerful tool, creating complex rules can be difficult or even impossible. This is where native SwiftLint rules come in. They're just like the rules that come with SwiftLint - they're written in Swift, efficient, and can catch more violations compared to regex.
The final project can be found at https://github.com/jpsim/swiftlint-bazel-example
Install Bazel if not installed.
brew list --formula | grep -q "bazel" || brew install bazel
Start by creating a new directory.
mkdir MySwiftLintRules && cd MySwiftLintRules
Next, set up a new Bazel workspace. This involves creating several files and enabling bzlmod:
touch .bazelrc WORKSPACE MODULE.bazel BUILD && echo "common --enable_bzlmod" > .bazelrc
Add the boilerplate code to the MODULE.bazel
module(
name = "swiftlint-bazel-example",
version = "0.0.0",
compatibility_level = 1
)
bazel_dep(name = "swiftlint", version = "0.51.0", repo_name = "SwiftLint")
extra_rules = use_extension("@SwiftLint//bazel:extensions.bzl", "extra_rules")
extra_rules.setup(srcs = "@swiftlint-bazel-example//swiftlint_extra_rules:extra_rules")
bazel_dep(name = "rules_xcodeproj", version = "1.4.0")
Add the boilerplate code to the BUILD
file. This will add Xcode support for our Bazel project.
load("@rules_xcodeproj//xcodeproj:defs.bzl", "xcodeproj")
xcodeproj(
name = "swiftlint_xcodeproj",
project_name = "SwiftLint",
tags = ["manual"],
top_level_targets = [
"@SwiftLint//:swiftlint",
"@SwiftLint//Tests:ExtraRulesTests",
],
)
Create a directory to store the source code for your rules.
mkdir swiftlint_extra_rules && touch swiftlint_extra_rules/Rules.swift && touch swiftlint_extra_rules/BUILD
And for the last time, paste the boilerplate code to the swiftlint_extra_rules/BUILD
file
filegroup(
name = "extra_rules",
srcs = glob(["**/*.swift"]),
visibility = ["//visibility:public"],
)
At this point, you should be able to build SwiftLint using the bazel build
command.
We’ve added rules_xcodeproj
dependency to our project to make developing your custom rules easier. So, let’s generate our project:
bazel run swiftlint_xcodeproj && open SwiftLint.xcodeproj -a Xcode
Now, we can build and debug the swiftlint
scheme. You can use this app as usual with swiftlint binary installed from the homebrew.
Try running it on your project: Click the scheme name in the toolbar of your project window, click “Edit Scheme…“, and set the "Working Directory" in the "Options" tab to the path where you would like to execute SwiftLint.
For more information on developing native SwiftLint rules, check out SwiftLint’s CONTRIBUTING.md
In this example, we are creating the "forbidden var rule," as shown in the video tutorial at https://vimeo.com/819268038.
Copy and paste this code into the Rules.swift
file, then run the swiftlint
scheme in Xcode.
import SwiftSyntax
// This function should return an array containing all of your custom rules
func extraRules() -> [Rule.Type] {
[ForbiddenVarRule.self]
}
struct ForbiddenVarRule: ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration(.error)
init() {}
static let description = RuleDescription(
identifier: "forbidden_var",
name: "Forbidden Var",
description: "Variables should not be called 'forbidden'",
kind: .idiomatic,
nonTriggeringExamples: [
Example("let notForbidden = 0")
],
triggeringExamples: [
Example("let ↓forbidden = 0")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension ForbiddenVarRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: IdentifierPatternSyntax) {
if node.identifier.text == "forbidden" {
violations.append(node.identifier.positionAfterSkippingLeadingTrivia)
}
}
}
}
And that's it! You've created a custom SwiftLint rule and integrated it into your Swift project using Bazel. With this setup, you can create and test as many custom rules as you need, helping to ensure that your code stays consistent and conforms to your team's coding standards.
Happy linting!