Skip to content
Open
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
1 change: 1 addition & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
bazel-*
.DS_Store
.bazelrc.user
.idea/
.ijwb/
Expand Down
5 changes: 3 additions & 2 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -50,14 +50,14 @@ Linters which are not language-specific:
| Pkl | [pkl] | |
| Protocol Buffer | [buf] | [buf lint] |
| Python | [ruff] | [bandit], [flake8], [pydoclint], [pylint], [ruff], [ty] |
| QML | [qmlformat] | [qmllint] |
| QML | [qmlformat] | [qmllint] |
| Ruby | | [RuboCop], [Standard] |
| Rust | [rustfmt] | [clippy] |
| SQL | [prettier-plugin-sql] | |
| Scala | [scalafmt] | |
| Shell | [shfmt] | [shellcheck] |
| Starlark | [Buildifier] | [Buildifier] |
| Swift | [SwiftFormat] (1) | |
| Swift | [SwiftFormat] (1) | [SwiftLint] (1) |
| TOML | [taplo] | |
| TSX | [Prettier] | [ESLint] |
| TypeScript | [Prettier] | [ESLint] |
Expand All @@ -75,6 +75,7 @@ Linters which are not language-specific:
[buf lint]: https://buf.build/docs/lint/overview
[eslint]: https://eslint.org/
[swiftformat]: https://github.com/nicklockwood/SwiftFormat
[swiftlint]: https://github.com/realm/SwiftLint
[terraform]: https://github.com/hashicorp/terraform
[buf]: https://docs.buf.build/format/usage
[keep-sorted]: https://github.com/google/keep-sorted
Expand Down
1 change: 1 addition & 0 deletions examples/README.md
Original file line number Diff line number Diff line change
Expand Up @@ -24,6 +24,7 @@ Each example is self-contained and shows:
- `proto/` - Protocol Buffer formatting and linting with Buf
- `starlark/` - Starlark formatting with Buildifier; linting with Buildifier
- `qml/` - QML formatting with qmlformat; linting with qmllint
- `swift/` - Swift formatting with SwiftFormat; linting with SwiftLint

## Multi-language Example

Expand Down
5 changes: 5 additions & 0 deletions examples/swift/.bazelrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
build:lint --aspects=//tools/lint:linters.bzl%swiftlint
build:lint --aspects=//tools/lint:linters.bzl%swiftlint_nested_config
build:lint --aspects=//tools/lint:linters.bzl%swiftlint_nested_deeper_nearest_config
build:lint --aspects=//tools/lint:linters.bzl%swiftlint_nested_without_declared_config
build:lint --aspects=//tools/lint:linters.bzl%swiftlint_nested_fix_config
3 changes: 3 additions & 0 deletions examples/swift/.gitattributes
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
# Keep the intentional SwiftLint fixtures stable when running aspect format.
src/lintme.swift rules-lint-ignored
src/nested_fix/lintme.swift rules-lint-ignored
4 changes: 4 additions & 0 deletions examples/swift/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
strict: true

only_rules:
- function_name_whitespace
6 changes: 6 additions & 0 deletions examples/swift/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
package(default_visibility = ["//visibility:public"])

exports_files([
".swiftlint.yml",
"SwiftLintBaseline.json",
])
7 changes: 7 additions & 0 deletions examples/swift/MODULE.aspect
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
# Aspect Extension setup for CLI "lint" and "format" commands
# This example is in rules_lint, so we use a local dependency.
axl_local_dep(
name = "rules_lint",
path = "../..",
auto_use_tasks = True,
)
7 changes: 6 additions & 1 deletion examples/swift/MODULE.bazel
Original file line number Diff line number Diff line change
@@ -1,4 +1,4 @@
"Bazel dependencies for Swift formatting example"
"Bazel dependencies for Swift formatting and linting example"

bazel_dep(name = "aspect_rules_lint")
local_path_override(
Expand All @@ -10,3 +10,8 @@ local_path_override(
format_tools = use_extension("@aspect_rules_lint//format:extensions.bzl", "tools")
format_tools.swiftformat()
use_repo(format_tools, "swiftformat", "swiftformat_mac")

# Swift linter tools
lint_tools = use_extension("@aspect_rules_lint//lint:extensions.bzl", "tools")
lint_tools.swiftlint()
use_repo(lint_tools, "swiftlint_binary")
76 changes: 69 additions & 7 deletions examples/swift/README.md
Original file line number Diff line number Diff line change
@@ -1,26 +1,88 @@
# Swift Formatting Example
# Swift Formatting and Linting Example

This example demonstrates how to set up formatting for Swift code using `rules_lint`.
This example demonstrates how to set up formatting and linting for Swift code using `rules_lint`.

## Supported Tools

### Formatters

- **SwiftFormat** - Code formatter for Swift

Note: No Swift linter is currently available in rules_lint.
### Linters

- **SwiftLint** - Linter for Swift

## Setup

1. Configure MODULE.bazel with required dependencies
2. Create the MODULE.aspect file to register CLI tasks
3. Configure Format Tools (add swiftformat)
4. Configure Formatters
4. Configure Lint Tools (add swiftlint)
5. Configure Formatters and Linters

- See `tools/format/BUILD` for how to set up the formatter
- See `tools/lint/linters.bzl` for how to set up the linter aspect

6. Perform formatting and linting using `aspect format` and `aspect lint`

## SwiftLint Configuration

SwiftLint policy such as enabled rules, severity, and human reporter should
live in `.swiftlint.yml`. Bazel target membership determines which files the
aspect lints. SwiftLint `excluded` entries are still honored, but `included`
entries should not be used to narrow explicitly listed Bazel source files.

The aspect only needs the binary and config labels for a typical setup:

- See `tools/format/BUILD.bazel` for how to set up the formatter
```starlark
swiftlint = lint_swiftlint_aspect(
binary = Label("//tools/lint:swiftlint"),
configs = [Label("//:.swiftlint.yml")],
)
```

5. Perform formatting using `aspect format`
For target-specific nested `.swiftlint.yml` files, declare the main config
first, then the nested configs, and set `config_mode = "nested"`:

```starlark
swiftlint = lint_swiftlint_aspect(
binary = Label("//tools/lint:swiftlint"),
configs = [
Label("//:.swiftlint.yml"),
Label("//src:nested/.swiftlint.yml"),
],
config_mode = "nested",
)
```

rules_lint selects the deepest declared nested config that contains all Swift
sources in the target and passes only the main config plus that nearest nested
config to SwiftLint with `--config`. This mirrors [SwiftLint's default nested
configuration behavior](https://github.com/realm/SwiftLint#nested-configurations);
intermediate ancestor configs are not accumulated for deeper files. SwiftLint
does not auto-discover repository config files at execution time.

Declare SwiftLint config hierarchy files in `configs`; prefer the aspect's
`baseline` argument over a `baseline` entry in `.swiftlint.yml`:

```starlark
swiftlint_with_baseline = lint_swiftlint_aspect(
binary = Label("//tools/lint:swiftlint"),
configs = [Label("//:.swiftlint.yml")],
baseline = Label("//:SwiftLintBaseline.json"),
)
```

## Example Code

See `src/hello.swift` for a simple example Swift file.
See `src/formatme.swift` for a simple Swift file that SwiftFormat can format.
See `src/lintme.swift` for a separate Swift file with an intentional SwiftLint
violation. That lint fixture is marked `rules-lint-ignored` in `.gitattributes`
so formatting the example does not erase the lint demo.

## Configuration Files

- `.swiftlint.yml` - SwiftLint configuration for the example
- `src/nested/.swiftlint.yml` - nested SwiftLint configuration fixture
- `src/nested/deeper/.swiftlint.yml` - nearest nested configuration fixture
- `SwiftLintBaseline.json` - SwiftLint baseline used by the integration test
13 changes: 13 additions & 0 deletions examples/swift/SwiftLintBaseline.json
Original file line number Diff line number Diff line change
@@ -0,0 +1,13 @@
[
{
"violation": {
"ruleIdentifier": "function_name_whitespace",
"severity": "error",
"reason": "Too many spaces between 'func' and function name",
"ruleDescription": "There should be consistent whitespace before and after function names and generic parameters.",
"ruleName": "Function Name Whitespace",
"location": { "file": "src/lintme.swift", "character": 9, "line": 2 }
},
"text": " func printme() {"
}
]
53 changes: 51 additions & 2 deletions examples/swift/src/BUILD
Original file line number Diff line number Diff line change
@@ -1,6 +1,55 @@
package(default_visibility = ["//visibility:public"])

exports_files([
"nested/.swiftlint.yml",
"nested/deeper/.swiftlint.yml",
"nested_fix/.swiftlint.yml",
])

filegroup(
name = "lint",
srcs = ["lintme.swift"],
tags = ["swift"],
)

filegroup(
name = "baseline",
srcs = ["lintme.swift"],
tags = ["swift-baseline"],
)

filegroup(
name = "default_config",
srcs = ["lintme.swift"],
tags = ["swift-default-config"],
)

filegroup(
name = "verbose",
srcs = ["lintme.swift"],
tags = ["swift-verbose"],
)

filegroup(
name = "nested_config",
srcs = ["nested/lintme.swift"],
tags = ["swift-nested-config"],
)

filegroup(
name = "nested_deeper_nearest_config",
srcs = ["nested/deeper/nearest.swift"],
tags = ["swift-nested-deeper-nearest-config"],
)

filegroup(
name = "nested_without_declared_config",
srcs = ["nested/lintme.swift"],
tags = ["swift-nested-without-declared-config"],
)

filegroup(
name = "all",
srcs = ["hello.swift"],
name = "nested_fix_config",
srcs = ["nested_fix/lintme.swift"],
tags = ["swift-nested-fix-config"],
)
3 changes: 3 additions & 0 deletions examples/swift/src/formatme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,3 @@
struct FormatDemo {
let message="Hello, World!"
}
Original file line number Diff line number Diff line change
@@ -1,6 +1,5 @@
class Controller {
func printme() {
func printme() {
print("Hello, World!")
}
}

4 changes: 4 additions & 0 deletions examples/swift/src/nested/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,4 @@
strict: true

only_rules:
- force_unwrapping
2 changes: 2 additions & 0 deletions examples/swift/src/nested/deeper/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
only_rules:
- empty_count
2 changes: 2 additions & 0 deletions examples/swift/src/nested/deeper/nearest.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let name: String? = "swift"
print(name!)
2 changes: 2 additions & 0 deletions examples/swift/src/nested/lintme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
let name: String? = "swift"
print(name!)
2 changes: 2 additions & 0 deletions examples/swift/src/nested_fix/.swiftlint.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
only_rules:
- function_name_whitespace
5 changes: 5 additions & 0 deletions examples/swift/src/nested_fix/lintme.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,5 @@
class NestedFixController {
func printme() {
print("Hello, World!")
}
}
84 changes: 84 additions & 0 deletions examples/swift/test/BUILD
Original file line number Diff line number Diff line change
@@ -0,0 +1,84 @@
"Demonstrates how to enforce zero-lint-tolerance policy with tests"

# buildifier: disable=bzl-visibility
load("@aspect_rules_lint//lint/private:machine_report_testing.bzl", "report_test")
load("//test:machine_output.bzl", "machine_swiftlint_nested_config_report", "machine_swiftlint_report", "machine_swiftlint_verbose_report")
load(
"//tools/lint:linters.bzl",
"swiftlint_baseline_test",
"swiftlint_default_config_test",
"swiftlint_nested_config_test",
"swiftlint_nested_deeper_nearest_config_test",
"swiftlint_nested_without_declared_config_test",
"swiftlint_test",
)

package(default_visibility = ["//visibility:public"])

swiftlint_test(
name = "swiftlint",
srcs = ["//src:lint"],
expected_exit_code = 2,
)

swiftlint_baseline_test(
name = "swiftlint_baseline",
srcs = ["//src:baseline"],
)

swiftlint_default_config_test(
name = "swiftlint_default_config",
srcs = ["//src:default_config"],
)

swiftlint_nested_config_test(
name = "swiftlint_nested_config",
srcs = ["//src:nested_config"],
expected_exit_code = 2,
)

swiftlint_nested_deeper_nearest_config_test(
name = "swiftlint_nested_deeper_nearest_config",
srcs = ["//src:nested_deeper_nearest_config"],
)

swiftlint_nested_without_declared_config_test(
name = "swiftlint_nested_without_declared_config",
srcs = ["//src:nested_without_declared_config"],
)

machine_swiftlint_report(
name = "machine_swiftlint_report",
src = "//src:lint",
)

machine_swiftlint_nested_config_report(
name = "machine_swiftlint_nested_config_report",
src = "//src:nested_config",
)

machine_swiftlint_verbose_report(
name = "machine_swiftlint_verbose_report",
src = "//src:verbose",
)

report_test(
name = "swiftlint_machine_output_test",
expected_tool = "SwiftLint",
expected_uri = "src/lintme.swift",
report = "machine_swiftlint_report",
)

report_test(
name = "swiftlint_nested_config_machine_output_test",
expected_tool = "SwiftLint",
expected_uri = "src/nested/lintme.swift",
report = "machine_swiftlint_nested_config_report",
)

report_test(
name = "swiftlint_verbose_machine_output_test",
expected_tool = "SwiftLint",
expected_uri = "src/lintme.swift",
report = "machine_swiftlint_verbose_report",
)
Loading
Loading