Compare commits

...

703 Commits

Author SHA1 Message Date
Danny Mösch 3ec9bc85bc Inline variables 2023-06-18 12:01:28 +02:00
Danny Mösch f5ea2492dc Merge allowed symbols with alphanumerics where possible 2023-06-18 12:01:28 +02:00
Danny Mösch b89e43a799
Make sure case check can be disabled by new `off` option value (#5064) 2023-06-17 22:27:57 +02:00
Danny Mösch c8cd311615 Collapse conditional statements into single expression 2023-06-13 23:24:48 +02:00
Danny Mösch ba42a6aa36 Extract examples 2023-06-13 23:24:48 +02:00
Danny Mösch 83ab977a1b Introduce visitor that tracks declared variables while traversing the AST 2023-06-13 23:24:48 +02:00
Danny Mösch f12b8d66d3
Do not trigger `prefer_self_in_static_references` rule on collection types (#5055) 2023-06-12 16:40:45 -04:00
JP Simard e29de7a99f
Update Bazel dependencies (#5060)
* rules_apple: 2.2.0 -> 2.3.0
* rules_swift: 1.7.1 -> 1.8.0
* rules_xcodeproj: 1.5.1 -> 1.7.0
2023-06-12 15:58:41 -04:00
JP Simard d32bf2b879
Update CryptoSwift (#5059)
Diff: https://github.com/krzyzanowskim/CryptoSwift/compare/1.7.1...1.7.2
2023-06-12 19:23:33 +00:00
JP Simard e5cf99088b
Update SwiftSyntax to 509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-05-a (#5058)
Diff: https://github.com/apple/swift-syntax/compare/509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-05-02-a...509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-05-a
2023-06-12 15:22:17 -04:00
JP Simard 7462187191
Add 5.9 to Swift version tests (#5057)
This gets all tests passing with Xcode 15 beta 1.
2023-06-12 18:39:18 +00:00
Danny Mösch 23326fc433
Specify default options in configurations only (#5056) 2023-06-11 10:44:06 -07:00
woxtu 156f6aabc8
Fix false positives in `sorted_first_last` rule when `first`/`last` have a predicate (#5038) 2023-06-11 17:22:06 +02:00
woxtu 43190834c5
Fix `legacy_random` examples (#5047) 2023-06-10 23:53:45 +02:00
woxtu f1d5a55658
Fix typo (#5044) 2023-06-10 23:50:49 +02:00
woxtu c735e6320f
Add Swift version 5.8.1 (#5050) 2023-06-05 16:52:35 +00:00
woxtu 2f9997e2cc
Fix typos (#5033) 2023-05-25 23:58:29 +02:00
woxtu 8f3eea06d3
Fix include path for custom `rule_id` rule (#5031) 2023-05-24 00:36:00 +02:00
woxtu a53a1f052b
Fix links to rules directory (#5029) 2023-05-22 21:37:48 +02:00
Danny Mösch 704c9ae116
Move ViolationsSyntaxVisitor into its own file (#5028) 2023-05-22 07:35:46 -04:00
Martin Redington 521df18fdb
Fix false alarms in `redundant_objc_attribute` rule (#4742) 2023-05-20 21:45:27 +02:00
Danny Mösch a2ba0a0626
Omit "Rule" in configuration names (#5024) 2023-05-20 04:39:04 -04:00
Hilton Campbell 1094a3b70e
Add `grouping` option to the `sorted_imports` rule (#4935) 2023-05-19 15:49:07 -04:00
Haocen Jiang 754127924f
Update replacement for CGRectIntersection (#5023) 2023-05-19 15:17:14 -04:00
Danny Mösch 3f039f26d5
Connect configs with their referencing rules to have some context in error logging (#5017)
With the binding of configurations to their associated rule types
"unknown configuration" errors can be made more specific mentioning
also the rule's identifier in the printed message.
2023-05-19 20:58:24 +02:00
Danny Mösch 671589b1d4
Let configuration names match rule names (#5021) 2023-05-18 12:04:39 -04:00
Danny Mösch d938b2d3c3
Specify default options in configurations only (#5020) 2023-05-18 17:21:30 +02:00
Danny Mösch 7fd22e0d6e
Use `SeverityBasedRuleConfiguration` where possible (#5019) 2023-05-18 16:04:20 +02:00
JP Simard 07740506b5
Automate adding a new changelog section after releasing (#5016) 2023-05-17 10:36:31 -04:00
kid cherish 26d06bcebb
[Update] Add Code Line in README_KR.md (#5015) 2023-05-17 07:05:37 +02:00
Danny Mösch fe312a06fe
Rely on pre-defined issues and allow to print them directly to the console (#5013)
Advantages of having typical errors at a common place:
* Error message styling can be harmonized
* Existing messages can be reused
* Overview of prefixes ('error: ', 'warning: ')
* Manage how to print them to the console
2023-05-16 20:15:14 +02:00
Danny Mösch d73970e9d7
Remove error wrapper (#5008) 2023-05-16 18:22:25 +02:00
Danny Mösch c0ea9d1925
Do not trigger `prefer_self_in_static_references` rule on `typealias` in classes (#5012) 2023-05-15 14:07:21 -04:00
Danny Mösch 405113f793
Open ConfigurationError up to be used more broadly (#5005) 2023-05-14 20:00:46 +02:00
Danny Mösch f4987071f0
Extract configuration into its own file (#5007) 2023-05-14 13:05:36 -04:00
Danny Mösch 306dec0fcc
Disable `no_fallthrough_only` in favor of simultaneously active `fallthrough` (#5006)
Avoids two violation when using `fallthrough`.
2023-05-14 15:55:02 +00:00
Danny Mösch 4d4a330d46
Silence `xct_specific_matcher` rule on types and tuples used in "one argument asserts" (#4997) 2023-05-13 22:04:35 +02:00
JP Simard 1e73835e83
Remove dead code (#5004)
Identified with the upcoming improved dead code detection.
2023-05-12 14:20:31 -04:00
JP Simard 866001db2f
Add new changelog section 2023-05-12 11:05:45 -04:00
JP Simard 34f5ffa7f7
release 0.52.2 2023-05-12 11:02:03 -04:00
JP Simard 1b1b19a902
Fix some `unhandled_throwing_task` false positives (#5001)
Fixes some of the cases reported in
https://github.com/realm/SwiftLint/issues/4987
2023-05-12 10:35:59 -04:00
JP Simard 15a18fd4e8
Merge `make release` with `make publish` (#5003)
To simplify the release process.
2023-05-12 09:58:06 -04:00
JP Simard 5802fd99b1
Parallelize TSan tests on CI (#5002) 2023-05-12 09:54:27 -04:00
JP Simard e7bf813f15
Make `unhandled_throwing_task` rule opt-in (#5000)
Because since it was included in a release a few days ago, we've had
several reports of false positives and so flipping to opt-in is
appropriate so users can determine for themselves if they should enable
the rule.
2023-05-12 09:46:07 -04:00
Danny Mösch 3cf9585b2e
Exclude `self.x = x` from being reported in the `redundant_self_in_closure` rule (#4996) 2023-05-11 23:26:39 +02:00
JP Simard 8827fca693
Automate GitHub Release Creation (#4995)
* Add `tools/generate-release-notes.sh` script
* Add `tools/create-github-release.sh` script
* Update `Releasing.md`
2023-05-11 14:01:29 -04:00
Marcelo Fabri 9f7c57b719
Make id and isVirtual public in SwiftLintFile (#4994) 2023-05-11 13:33:48 -04:00
JP Simard 73acbaf6d5
Add new changelog section 2023-05-11 11:26:23 -04:00
JP Simard 5616d858bc
release 0.52.1 2023-05-11 11:13:58 -04:00
JP Simard 6cdb0aa689
Update `make publish`
To use the CocoaPods version from bundler and to remove the stale
workaround.
2023-05-11 11:03:28 -04:00
JP Simard 7483e61615
Fix Bazel release tarball for compiling on macOS (#4992)
Fixes #4985
2023-05-11 10:59:02 -04:00
Kyle Bashour 87e230ce87
Fix false positive when result is accessed (#4986) 2023-05-10 20:36:00 -04:00
JP Simard 7f3b93147a
Update Gemfile.lock (#4983)
This happened automatically when I ran `bundle install` locally.
2023-05-10 17:23:50 -04:00
JP Simard 7501d44b69
Add new changelog section 2023-05-10 16:46:19 -04:00
Danny Mösch 0ffe0c5911 Extract tests for NameConfiguration 2023-05-10 22:20:30 +02:00
Danny Mösch c99a4286b8 Let `validates_start_with_lowercase` option in name configurations expect a severity 2023-05-10 22:20:30 +02:00
Danny Mösch 8d5700afca Remove deprecation warning in `identifier_name` rule
It was introduced 6 years ago.
2023-05-10 22:20:30 +02:00
Danny Mösch 1fa8e484c4 Add expiring TODO comments on deprecation warnings 2023-05-10 21:44:20 +02:00
Danny Mösch 9e15e37255 Disable `todo` in favor of `expiring_todo` 2023-05-10 21:44:20 +02:00
JP Simard 97259e24d0
release 0.52.0 2023-05-10 15:15:09 -04:00
JP Simard eb6d50a04c
Update SwiftSyntax to 509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-04-25-b (#4982) 2023-05-10 15:02:58 -04:00
Danny Mösch 9c1678968f
Remove deprecation warnings introduced > 2 years ago (#4972) 2023-05-10 14:05:28 -04:00
JP Simard fa32fbc4f0
Minor edits to `unhandled_throwing_task` changelog entry
And rule description.
2023-05-10 14:04:42 -04:00
Kyle Bashour 8822d40687
Add `unhandled_throwing_task` rule (#4958)
This rule will check Task's that are not explicitly annotated with success and
failure types for unhandled try expressions. These trys will silently fail if an
error is thrown.

See this forum thread for more details:
https://forums.swift.org/t/task-initializer-with-throwing-closure-swallows-error/56066
2023-05-10 14:03:01 -04:00
JP Simard c0cf1bf5c9
Add changelog entry for module split 2023-05-10 13:23:32 -04:00
JP Simard 444653bfd5
Update bazel version to 6.2.0 (#4981) 2023-05-10 16:25:34 +00:00
Danny Mösch 9138213121
Mark change as breaking (#4969) 2023-05-06 20:46:43 +00:00
Danny Mösch 6fd5f98d38 Add examples for weak variables to `implicitly_unwrapped_optional` rule 2023-05-06 22:46:25 +02:00
Danny Mösch bc18ff14e3 Add violation markers 2023-05-06 22:46:25 +02:00
Andyy Hope 5e15039554
Move `excludedPaths` out of iteration loop to speed up lint times (#4955) 2023-05-06 16:43:33 -04:00
Danny Mösch 7756793356
Add new option to `attributes` rule to influence it for attributes with arguments (#4855) 2023-05-05 16:12:01 -04:00
Danny Mösch f0138ea1df
Add example to proof that shorthand optional bindings work with `unused_declaration` rule (#4857) 2023-05-05 15:22:59 -04:00
Danny Mösch 6a2e973de3
Add new `redundant_self_in_closure` rule (#4911) 2023-05-05 21:14:14 +02:00
JP Simard e86c06c390
[SwiftLintCore] Make `focused()` public (#4959)
So it can be used from SwiftLintBuiltInRules.

I missed this when splitting the SwiftLintFramework module.
2023-05-04 15:07:37 +00:00
JP Simard 6b094dd711
[SwiftSyntax] Update to latest 509.0.0 tag (#4920)
* [bazel] Remove custom SwiftSyntax BUILD file

Something similar to this has been merged upstream instead now. This
also renames the repo name to SwiftSyntax in preparation for it being in
the BCR

* [SwiftSyntax] Update to latest 509.0.0 tag

https://github.com/apple/swift-syntax/releases/tag/509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-04-25-b
2023-05-03 11:34:26 -04:00
Simon Støvring 9054ec0658
Add test verifying that the `file_header` rule can require an empty file header (#4928) 2023-05-01 18:47:37 +02:00
Chris White 71b89e66de
Prepend `warning: ` to error messages (#4927) 2023-05-01 18:26:25 +02:00
JP Simard a7bc9e20c7
Move built-in rules to new SwiftLintBuiltInRules module (#4950) 2023-04-27 11:16:01 -04:00
JP Simard eaf34d7204
Move extra rules into its own module (#4949)
This will decouple building extra rules from built-in rules so that
making changes in one of those modules doesn't rebuild the other.
2023-04-27 10:37:30 -04:00
JP Simard 86d60400c1 Move core SwiftLint functionality to new SwiftLintCore module
Over the years, SwiftLintFramework had become a fairly massive monolith,
containing over 400 source files with both core infrastructure and
rules.

Architecturally, the rules should rely on the core infrastructure but
not the other way around. There are two exceptions to this:
`custom_rules` and `superfluous_disable_command` which need special
integration with the linter infrastructure.

Now the time has come to formalize this architecture and one way to do
that is to move the core SwiftLint functionality out of
SwiftLintFramework and into a new SwiftLintCore module that the rules
can depend on.

Beyond enforcing architectural patterns, this also has the advantage of
speeding up incremental compilation by skipping rebuilding the core
functionality when iterating on rules.

Because the core functionality is always useful when building rules, I'm
opting to import SwiftLintCore in SwiftLintFramework as `@_exported` so
that it's implicitly available to all files in SwiftLintFramework
without needing to import it directly.

In a follow-up I'll also split the built-in rules and the extra rules
into their own modules. More modularization is possible from there, but
not planned.

The bulk of this PR just moves files from `Source/SwiftLintFramework/*`
to `Source/SwiftLintCore/*`. There are some other changes that can't be
split up into their own PRs:

* Change jazzy to document the SwiftLintCore module instead of
  SwiftLintFramework.
* Change imports in unit tests to reflect where code was moved to.
* Update `sourcery` make rule to reflect where code was moved to.
* Create a new `coreRules` array and register those rules with the
  registry. This allows the `custom_rules` and
  `superfluous_disable_command` rule implementations to remain internal
  to the SwiftLintCore module, preventing more implementation details
  from leaking across architectural layers.
* Move `RuleRegistry.registerAllRulesOnce()` out of the type declaration
  and up one level so it can access rules defined downstream from
  SwiftLintCore.
2023-04-26 21:10:19 -04:00
JP Simard 740572f049 Move `Version.swift` to SwiftLintCore 2023-04-26 21:10:19 -04:00
JP Simard 3541ef4d02 Import SwiftLintCore in generated tests
In preparation for it containing the `superfluous_disable_command` rule.
2023-04-26 21:10:19 -04:00
JP Simard 39a05f2c35 Re-export SwiftLintCore import in SwiftLintFramework
Because the core functionality is always useful when building rules, I'm
opting to import SwiftLintCore in SwiftLintFramework as `@_exported` so
that it's implicitly available to all files in SwiftLintFramework
without needing to import it directly.
2023-04-26 21:10:19 -04:00
JP Simard 711d92e26f Add empty SwiftLintCore module
Source files will be added to it in a follow-up commit.
2023-04-26 21:10:19 -04:00
JP Simard d8dfc3c937
Update rules_xcodeproj to 1.5.1 (#4948)
Lots of performance optimizations in this release

https://github.com/MobileNativeFoundation/rules_xcodeproj/releases/tag/1.5.1
2023-04-26 21:06:17 -04:00
JP Simard 3a2bb15ca3
Make some SwiftLintFramework declarations public (#4945)
* Make some SwiftLintFramework declarations public

If built-in rules depend on them.

This will allow separating the rules from the core infrastructure in
separate modules in an upcoming PR.

It also helps formalize the API contract for what should be accessible
to rule implementations.

* Exclude extensions from jazzy
2023-04-26 16:37:25 -04:00
JP Simard a7e3909c6a
Improve StyleViolation reason formatting validation (#4946)
So it checks violations for all rules in the registry, not just the
built-in rules.
2023-04-26 18:54:44 +00:00
JP Simard 72c2a5488d
Re-order `CustomRules.swift` (#4944)
For consistency with other files in this project, which usually have
private extensions at the bottom of the file.
2023-04-26 18:37:40 +00:00
JP Simard ea56405983
Rename `Request.cursorInfo()` to `cursorInfoWithoutSymbolGraph()` (#4943)
To avoid conflicting with the `cursorInfo` declared in
SourceKittenFramework.
2023-04-26 18:28:24 +00:00
JP Simard 9c414932d8
Add more documentation comments (#4942)
In preparation for these declarations to become public.
2023-04-26 14:18:13 -04:00
JP Simard 8a21549ca9
Change `Reachability` to an enum (#4941)
Since it's already acting as a namespace, might as well make it so it
can't be instantiated and can't have instance properties.
2023-04-26 13:55:17 -04:00
JP Simard 4abcf5f0dd
Remove `SwiftLintFramework` dependency from GeneratedTests target (#4940)
Since it's available transitively from SwiftLintTestHelpers.
2023-04-26 16:37:23 +00:00
JP Simard b2a4c9fcb9
Mark `RulesFilter` as `final class` (#4939)
Since it's not designed to be subclassed.
2023-04-26 16:26:22 +00:00
JP Simard 603fff9a82
Make `Correction` and `CorrectableRule` declarations public (#4937)
The CLI target shouldn't be importing SwiftLintFramework with
`@_spi(TestHelper)`. If the CLI target needs to access something in
SwiftLintFramework, that declaration should be `public`.
2023-04-26 12:17:10 -04:00
JP Simard 2544dc79d3
Move `CustomRuleTimer` to its own file (#4938)
And add docs.
2023-04-26 15:56:04 +00:00
Kris Kline ea6b3b4d64
Update minimum OS versions for Xcode 14.3 (#4934)
* iOS - 11.0
* MacOS - 10.13
* tvOS - 11.0
* WatchOS - 7.0
2023-04-25 19:51:36 +00:00
Benny Wong 172b8b8a94
Remove `else else` typo (#4933) 2023-04-25 18:03:25 +00:00
JP Simard 4c04cdafa7 Introduce SwiftLintTestCase
To consistently set up rules before tests
2023-04-25 12:28:50 -04:00
JP Simard 165172e0fa Introduce a "rule registry" concept
This will allow for registering rules that aren't compiled as part of
SwiftLintFramework.

Specifically this will allow us to split the built-in and extra rules
into separate modules, leading to faster incremental compilation when
working on rules since the rest of the framework won't need to be
rebuilt on every compilation.
2023-04-25 12:28:50 -04:00
JP Simard 3f52acd0a2
Make `UnitTestRuleConfiguration` internal (#4931) 2023-04-25 13:28:28 +00:00
JP Simard e14a73438f
Remove SourceKitten from OSSCheck (#4930)
It looks like there's some non-determinism in how we lint it that was
recently introduced, maybe it's due to some SwiftSyntax update, not
sure.
2023-04-25 09:27:24 -04:00
JP Simard 817de197d5
Clean up `MARK` comments 2023-04-24 15:13:44 -04:00
JP Simard 214a749ace
Extend the custom rules section in the README (#4926)
To link to a video tutorial and example project for creating custom
rules written in Swift and to outline some of the advantages to using
these over regex custom rules.
2023-04-24 17:04:56 +00:00
JP Simard fd4f9afb08
Remove AppCode section in README.md (#4925)
> AppCode is no longer available as a commercial product as of
> December 14, 2022.

From https://blog.jetbrains.com/appcode/2022/12/appcode-2022-3-release-and-end-of-sales-and-support/
2023-04-24 12:44:49 -04:00
Danny Mösch c241935635
Introduce basic Stack type (#4922) 2023-04-23 09:12:35 +00:00
JP Simard 46ff727a13
Update Bazel version to 6.1.2 (#4918) 2023-04-21 17:00:53 -04:00
JP Simard dc89109622
Apply suggested bazel flags (#4917) 2023-04-21 16:51:14 -04:00
JP Simard 9c2525139a
Update CryptoSwift to 1.7.1 (#4916)
To fix Swift 5.8 warnings
2023-04-21 14:15:44 +00:00
JP Simard d601917e40
Apply minor changelog edits 2023-04-21 09:25:11 -04:00
YoungBin Lee 498b4c8fbe
Correct organization name in README files (#4913)
Rename 'Ray Wenderlich' to 'Kodeco'.
2023-04-21 05:16:13 +00:00
Danny Mösch 69fadb6918
Remove no-op initializers from structs (#4912) 2023-04-20 11:59:37 +02:00
JP Simard f866ec32b1
Add link to video tutorial demonstrating how to write a rule (#4910)
https://vimeo.com/819268038
2023-04-19 17:16:27 -04:00
JP Simard 6d51459323
Update Ruby Gems (#4909)
* danger: 8.6.1 -> 9.2.0
* cocoapods: 1.11.3 -> 1.12.1
* jazzy: 0.14.2 -> 0.14.3
2023-04-19 10:23:21 -04:00
JP Simard 21dc766c93
Exclude new `XCTSpecificMatcherRule` examples from documentation (#4907) 2023-04-18 21:45:51 +00:00
JP Simard 0f802f766a
[CI] Remove `update_swift_syntax.yml` (#4906)
We now manually update SwiftSyntax as needed.
2023-04-18 21:35:43 +00:00
JP Simard fbbccf9db8
Allow configuring `xct_specific_matcher` with matchers (#4905)
So that either `one-argument-asserts` or `two-argument-asserts` or both
can be enabled.

The following configuration effectively reverts back to the rule
behavior prior to https://github.com/realm/SwiftLint/pull/3858:

```yaml
xct_specific_matcher:
  matchers:
    - two-argument-asserts
```
2023-04-18 20:50:58 +00:00
JP Simard 8b72eb0406
Mark `bazel_skylib` as `dev_dependency = True` (#4904)
So folks who integrate SwiftLint with Bazel don't need to match this
exact version.
2023-04-18 18:50:56 +00:00
JP Simard c22d60fac0
Update `UnusedImportRuleExamples` for Swift 5.8 (#4903) 2023-04-18 17:58:40 +00:00
Keith Smiley 1892c846b0
Workaround dyld warning about SwiftSyntax classes (#4901)
This uses a recent but unannounced (also read as private) feature of
dyld where it ignores duplicate Objective-C classes when they're in a
special format in the binary. c8a445f88f/dyld/PrebuiltLoader.cpp (L1660-L1662)

I think this is generally safe because hopefully people aren't actually
using the SwiftSyntax classes through the Objective-C runtime, but if
they are we'd still probably prefer to silence the noise and accept the
UB.

Fixes https://github.com/realm/SwiftLint/issues/4782
2023-04-17 19:55:33 -04:00
Andrew Montgomery 41290a23d3
Remove checks for setUp/tearDown from `overridden_super_call` rule (#4875) 2023-04-17 20:18:41 +00:00
Frad LEE 5814ec1a1a
Add architecture detection to Xcode script configuration (#4873) 2023-04-17 18:48:06 +00:00
JP Simard f9b0e9f0af
Update CryptoSwift to 1.7.0 (#4899) 2023-04-17 10:37:40 +00:00
Keith Smiley 3ef44cf742
Switch to SwiftPM conditional dependencies API (#4003)
This API allows us to mostly remove `#if` conditionals which enables
cross compilation from macOS -> Linux. It also removes the thrashing of
Package.resolved if you compile in a docker image.
2023-04-16 20:19:25 +02:00
Danny Mösch 5aed7ce95c
Check first argument label in `reduce_boolean` rule (#4895) 2023-04-16 09:58:27 +02:00
Martin Redington 97fd216455
Skip unit tests in `no_magic_numbers` rule (#4897) 2023-04-16 09:07:54 +02:00
Danny Mösch f127ba14dd
Ignore block comments in `let_var_whitespace` rule (#4889) 2023-04-16 00:10:03 +02:00
JP Simard 74b82daba0
[CI] Use rbenv to manage Ruby (#4893)
And pin the Ruby version to 2.7.5. rbenv was installed on all CI
machines using Homebrew, with Ruby 2.7.5 installed and set as the
global version. The `pre-command` script was also updated on all agents
to add rbenv binaries to the path and run `rbenv init`.

Remove rules_ruby integration and just use rbenv and ruby directly.
2023-04-14 16:23:05 +00:00
JP Simard 8c617d7412
[CI] Update Azure Pipelines to run with Swift 5.8 (#4890)
Linux only because Azure Pipelines doesn't have any
images with macOS 13 or Xcode 14.3 yet.
2023-04-14 14:27:03 +00:00
JP Simard a4f4680442
Update copyright year for html docs 2023-04-14 10:18:47 -04:00
JP Simard 62b2914271
Update rules_ruby (#4891)
Just to stay up to date.
2023-04-14 10:11:50 -04:00
JP Simard 1b0f37c6f6
Pin Sourcery version to 2.0.2 (#4888)
By adding a `tools/sourcery` script that downloads and runs Sourcery via
Bazel.

Previously, unrelated changes might include modifications to the
generated comment headers because contributors' local versions of
Sourcery would be used, which we don't control.

Also move the CI job to Buildkite where the bazel server is usually
already warmed up and running.
2023-04-13 20:09:26 +00:00
JP Simard 5226725689
Fix extra space before member access 2023-04-13 15:12:37 -04:00
JP Simard e361e14c90
Move changelog entry to the latest section 2023-04-13 15:04:13 -04:00
JP Simard 70a56a1420
Update SwiftSyntax to 04-10 snapshot (#4887)
https://github.com/apple/swift-syntax/releases/tag/509.0.0-swift-5.9-DEVELOPMENT-SNAPSHOT-2023-04-10-a
2023-04-13 14:04:43 -04:00
Gabriel Féron 409f117da9
Use Ubuntu 20.04 to release Linux binaries (#4882)
* Use Ubuntu 20.04 to release Linux binaries

Using the latest version of Ubuntu leads to incompatibilities with `glibc` versions from LTS versions.

* Use Ubuntu 20.04 for Azure pipelines
2023-04-13 13:25:43 -04:00
JP Simard 7fe7bb28d9
Update rules_xcodeproj to 1.4.0 (#4885)
https://github.com/MobileNativeFoundation/rules_xcodeproj/releases/tag/1.4.0
2023-04-13 15:25:17 +00:00
Danny Mösch bb11e6a0a7 Extract examples to separate file 2023-04-04 20:10:59 +02:00
Danny Mösch eee97a7b2b Trigger `prefer_self_in_static_references` rule on types 2023-04-04 20:10:59 +02:00
Danny Mösch cf1e2e27dc Use `is` instead of `as` for simple type checks 2023-04-04 20:10:59 +02:00
Sven Münnich a2facce70c
Fix false positives related to multiline strings in `indentation_width` rule (#4862) 2023-04-04 19:27:16 +02:00
Danny Mösch bd444fcd77
Fix `lower_acl_than_parent` rule rewriter by preserving leading whitespace (#4861) 2023-04-03 20:26:46 +00:00
Danny Mösch ee849bcb10
Remove unused imports (#4856) 2023-04-03 19:26:59 +02:00
Danny Mösch 6d4bc78cb4
Add new `superfluous_else` rule (#4696) 2023-04-02 12:32:40 +02:00
Danny Mösch 16e2bb0f18
Extend `xct_specific_matcher` rule to check for boolean asserts on (un)equal comparisons (#3858) 2023-04-02 12:30:21 +02:00
Kim de Vos b0cbb440c3
Add new `sorted_enum_cases` rule (#4845) 2023-04-02 11:33:20 +02:00
Danny Mösch 58a07eb452
Update SwiftSyntax (#4852) 2023-03-31 23:12:31 +02:00
Danny Mösch 82491e6f84
Add Swift version 5.8 (#4851) 2023-03-31 22:44:25 +02:00
Danny Mösch 3d15419adb
Fix warnings in Swift 5.8/Xcode 14.3 (#4850) 2023-03-31 22:13:20 +02:00
Martin Redington 6a09af169e
Fix crash when parsing apple/swift (#4828) 2023-03-31 20:59:47 +02:00
Martin Redington 7dad240ea7
Add a `reporters` subcommand (#4836) 2023-03-31 08:09:01 +02:00
Martin Redington ca43d2359b
Fix unit test configuration (#4847) 2023-03-30 22:46:31 +02:00
JP Simard da27f1c7fd
Add empty changelog section 2023-03-27 12:11:36 -04:00
JP Simard eb85125a5f
release 0.51.0 2023-03-27 11:44:47 -04:00
JP Simard 62b9c2de7a
Don't run `make clean` automatically on `build`
Since when releasing, many steps depend on the `build`, so we don't want
to clean mid-way through releasing.

Explicitly run `make clean` at the start of `make release`.
2023-03-27 11:22:00 -04:00
JP Simard aeee6325af
Update bazel dependencies (#4842)
* Update bazel from 6.0.0 to 6.1.1
* Update rules_apple from 2.0.0 to 2.2.0
* Update rules_swift from 1.5.1 to 1.7.1
* Update rules_xcodeproj from 1.1.0 to 1.3.3
2023-03-27 14:36:44 +00:00
Martin Redington 5a91b1e392
Fixed display of optin and correctable (#4841)
Co-authored-by: Martin Redington <mildm8nnered@gmail.com>
2023-03-27 07:48:51 +02:00
JP Simard efc5afd5d5
Apply small formatting fixes (#4840)
* Trim excess trailing and vertical whitespace
* Move `SwiftyTextTable` dependency into `frameworkDependencies`
* Adjust wording in README around analyzer rules in the `opt_in_rules`
  section, since analyzer rules can only go in the `analyzer_rules`
  section.
2023-03-26 16:09:43 -04:00
Martin Redington f2d15355be
Add a `summary` table reporter (#4820) 2023-03-25 08:28:42 +01:00
Martin Redington a840058cf5
Add default.profraw to .gitignore and delete obsolete instructions (#4835)
Co-authored-by: Martin Redington <mildm8nnered@gmail.com>
2023-03-25 08:21:22 +01:00
dependabot[bot] 358bdc89a5
Bump activesupport from 6.1.7.1 to 6.1.7.3 (#4824)
Bumps [activesupport](https://github.com/rails/rails) from 6.1.7.1 to 6.1.7.3.
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v7.0.4.3/activesupport/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v6.1.7.1...v6.1.7.3)

---
updated-dependencies:
- dependency-name: activesupport
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-03-24 15:27:01 -04:00
강수진 a81a532091
Fix SwiftLint rule count in Korean README (#4832) 2023-03-22 20:08:28 +01:00
Martin Redington 6983c813c8
Fix rewriter of `trailing_semicolon` rule wrongly removing trailing comments (#4818) 2023-03-13 22:23:35 +00:00
Danny Mösch fb89ab2fb5 Stop triggering `unused_capture_list` on variables referenced in optional bindings 2023-03-12 14:38:10 +01:00
Danny Mösch 7ac128c83d Enable `direct_return` rule in the repository 2023-03-12 14:36:07 +01:00
Danny Mösch 06578e5d91 Get rid of some more disabled commands 2023-03-12 00:03:02 +01:00
Danny Mösch 1940e0e2d2 Make custom rule `rule_id` more specific 2023-03-12 00:03:02 +01:00
Danny Mösch bd77cbcf6e
Add example verifying that `trailing_semicolon` works with trailing comments (#4808) 2023-03-11 13:29:38 +00:00
Danny Mösch 1bf2f25e40
Get rid of some disabled commands (#4807) 2023-03-11 08:20:47 -05:00
Danny Mösch 7d7bee5eee
Disable deprecated rules (#4806) 2023-03-11 12:48:04 +00:00
Martin Redington 31510b662e
Use `all` optin rules facility for SwiftLint's own `.swiftlint.yml` (#4800) 2023-03-11 11:59:05 +01:00
Martin Redington 0bd8a7aba6
Add new `blanket_disable_command` rule (#4731) 2023-03-07 21:43:53 +01:00
Keith Smiley 084ad9dfd4
Remove unused _SwiftSyntaxMacros library (#4802)
This one was actually renamed to not have the underscore but it's not
used so we might as well drop it.
2023-03-07 17:54:46 +00:00
Martin Redington 228d53787b
Stop`superfluous_disable_command` from firing when disabled (#4791) 2023-03-05 19:21:35 +01:00
1in1 4fee8e6a0c
Add new `duplicate_conditions` rule (#4771) 2023-03-05 11:05:23 +01:00
Danny Mösch 4f9a608cf4
Remove duplicate line (#4797) 2023-03-04 13:12:59 +00:00
Martin Redington 1c3c62e422
Add new `invalid_swiftlint_command` rule (#4546) 2023-03-04 13:53:27 +01:00
Martin Redington d6e3bbb64d
Add "all" pseudo-rule which enables all opt-in rules (#4544) 2023-03-04 13:49:00 +01:00
Danny Mösch 128f37a6b8
Fix whitespace issue in rewriter of `redundant_optional_initialization` rule (#4795) 2023-03-04 11:47:24 +00:00
Tony Arnold cfd9a26e33
Ensure that configuration exclusion rules are respected by the build plugin (#4757)
We always pass all of the Swift source files in the target to the tool, so we need to ensure that any exclusion rules in the configuration are respected.
2023-03-02 22:26:49 +01:00
David Davies-Payne a71f588637
Add instructions for disabling Xcode package validation (#4751) 2023-02-28 19:40:23 +00:00
Martin Redington d73d87ac97
Add `include_multiline_strings` option to `indentation_width` rule (#4785) 2023-02-26 11:52:29 +01:00
Martin Redington 04791929a7
Improve `identifier_name` documentation (#4784) 2023-02-25 20:02:02 +01:00
Danny Mösch c9b1b961f5
Fix some issues in `direct_return` rule (#4783) 2023-02-25 02:48:31 -05:00
Danny Mösch 613e916c39
Use parameter names in description (#4781) 2023-02-24 15:55:39 +00:00
JP Simard e2f3f3ad45
Include `MODULE.bazel` in bazel release archive (#4779)
So it can be used with bzlmod.
2023-02-22 12:29:26 -05:00
JP Simard 5c94a43352
Update SourceKitten for Bazel without bzlmod (#4778)
Missed this in #4776.
2023-02-20 22:32:58 +00:00
JP Simard 183c0aa784
Set `--platform linux/amd64` in `make zip_linux_release`
So it works on other architectures (e.g. Apple Silicon)
2023-02-20 17:00:48 -05:00
JP Simard ed6793f8f1
Fix changelog whitespace formatting 2023-02-20 16:38:40 -05:00
JP Simard 1790ad56dd
Update SourceKitten & Yams (#4776) 2023-02-20 15:50:14 -05:00
JP Simard 2f0e537f9b
Update SwiftSyntax to latest development snapshot (#4759)
* Merge `spacedBinaryOperator` and `unspacedBinaryOperator`

* New contextual keyword enums

* Update for removed `UnavailabilityConditionSyntax`

* Handle how attributes are now defined

This partially reverts commit 325d0ee1e4.

* Handle removal of `TokenListSyntax`

* Update `Package.swift`

* Extract some SwiftSyntax helpers

* Update to `0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a`

* Skip attributes with keypath arguments in `attributes` rule

To preserve the rule's existing behavior.

* Limit unowned_variable_capture violations to capture lists

* Add changelog entries
2023-02-20 10:51:31 -05:00
Danny Mösch 393318d903
Add new `direct_return` opt-in rule (#4717) 2023-02-19 13:09:41 +01:00
Danny Mösch f3e5557de9
Stop triggering `strict_fileprivate` rule on protocol implementations (#4705) 2023-02-18 13:23:26 +01:00
Mathias Schreck 352ffdfc57
Fix void_return rule to support async and async throws functions (#4772) 2023-02-17 21:39:32 +01:00
Thi Doãn 6a9e6776a9
Update BUILD file for SwiftSyntax (#4768) 2023-02-16 13:25:16 -05:00
Danny Mösch ec38c244fd
Refactor rule list documentation (#4763) 2023-02-13 21:22:10 +01:00
Vasiliy Kattouf ad29864d7f
Add markdown local links to listed rules (#4762) 2023-02-12 23:22:09 +01:00
Marcelo Fabri b9e5cfb202
Fix correction on `lower_acl_than_parent` rule (#4761)
Fixes #4753
2023-02-09 01:47:33 -08:00
Michael S 9c216baf20
Add new `relative-path` reporter (#4739) 2023-02-07 23:23:00 +01:00
Marcelo Fabri 7eb479d546
Rewrite MultilineArgumentsRule using SwiftSyntax (#4750)
* Rewrite MultilineArgumentsRule using SwiftSyntax

* Add examples and changelog

Fixes #3399, #3605
2023-02-07 02:55:21 -08:00
Danny Mösch aafc574e90
Allow to pass a rule identifier to the `swiftlint docs` command (#4710) 2023-02-07 00:01:52 +01:00
Danny Mösch 5af4291b53
Let Emoji reporter print rule identifier (#4708) 2023-02-06 16:28:07 -05:00
Danny Mösch d3ebfc5567
Let `number_separator` rule trigger on misplaced separators (#4685) 2023-02-06 16:22:31 -05:00
Marcelo Fabri 60d0dd8a05
Update SwiftSyntaxRule.swift (#4749) 2023-02-06 14:16:18 -05:00
Marcelo Fabri 0163ffd328
Cache folded syntax tree (#4744) 2023-02-06 10:39:36 -08:00
JP Simard eb5712582f
Update rules_xcodeproj to 1.0.1 (#4748)
https://www.buildbuddy.io/blog/introducing-rules_xcodeproj-1-0
2023-02-06 18:08:11 +00:00
JP Simard 6ea0397620
Replace `if_let_shadowing` with `shorthand_optional_binding` (#4747)
In `.swiftlint.yml`
2023-02-06 16:12:08 +00:00
Marcelo Fabri aba0f63704
Speed up class_delegate_protocol rule (#4743) 2023-02-05 18:45:24 -08:00
Martin Redington 993f34a96c
Document `allow_zero_lintable_files` and move the `analyzer_rules` section up (#4741)
Co-authored-by: Martin Redington <mildm8nnered@gmail.com>
2023-02-04 13:43:41 +01:00
Christian Clauss eaacd9873e
Show how pre-commit can apply fixes and fail on errors (#4740) 2023-02-04 13:39:25 +01:00
Roman Stetsenko 843198d241
Use prebuildCommand for SwiftLint plugin (#4680) 2023-02-02 19:25:25 +01:00
Danny Mösch 325d0ee1e4
Consider custom attributes in `attributes` rule (#4616) 2023-01-31 22:34:11 +01:00
Danny Mösch 5eed8fe91b
Enable `if_let_shadowing` rule and fix all violations (#4247) 2023-01-31 22:31:38 +01:00
Jim Puls 60610cef84
Update Package.resolved (#4736) 2023-01-31 13:41:26 -05:00
JP Simard ca09bc5a22
Update rules_xcodeproj & Yams (#4735)
* rules_xcodeproj: https://github.com/buildbuddy-io/rules_xcodeproj/releases/tag/1.0.0rc1
* Yams: https://github.com/jpsim/Yams/releases/tag/5.0.4
2023-01-31 16:51:16 +00:00
Danny Mösch 0796236031
Use text blocks in reporters if reasonable (#4733) 2023-01-30 16:38:07 -05:00
Patrick 651b00eb70
Updated JUnit reporter (#4725) 2023-01-30 20:48:29 +01:00
Danny Mösch 500f143c7d
Remove unused code (#4729) 2023-01-28 19:50:22 +01:00
JP Simard 81f1dbfd1b
[CI] Stop running "Analyze" job (#4728)
it's extremely time-consuming, taking 25 minutes lately, and we'll
hopefully have a better solution soon with the upcoming improvements
to dead code detection, taking less than 1 second to run.

```
(1/4) Collecting units and records (0.25s)
(2/4) Collecting protocol conformances (0.26s)
(3/4) Collecting declarations and references (0.04s)
(4/4) Calculating unused declarations (0.05s)
Source/SwiftLintFramework/Extensions/Configuration+IndentationStyle.swift:16:18: error: Unused declaration named 'init(_:)'
Source/SwiftLintFramework/Models/LinterCache.swift:53:13: error: Unused declaration named 'init(cache:location:fileManager:swiftVersion:)'
Source/SwiftLintFramework/Models/SwiftVersion.swift:25:16: error: Unused declaration named 'fiveDotTwo'
Source/SwiftLintFramework/Models/SwiftVersion.swift:27:16: error: Unused declaration named 'fiveDotThree'
Source/SwiftLintFramework/Models/SwiftVersion.swift:31:16: error: Unused declaration named 'fiveDotFive'
Source/SwiftLintFramework/Rules/Lint/CompilerProtocolInitRule.swift:85:5: error: Unused declaration named 'init(protocolName:types:arguments:)'
Found 6 unused declarations (0.61s)
```
2023-01-27 19:51:13 -05:00
JP Simard 0750d5d465
Extract `EnumDeclSyntax.supportsRawValues` helper (#4727)
I'll want to use this elsewhere shortly.

Also adds `CGFloat` to the raw value types since we were missing that
one.
2023-01-27 16:46:54 -05:00
JP Simard 84c6d200b6
Clean up unused initializers (#4726)
Found with the upcoming improve dead code detection.
2023-01-27 16:31:56 -05:00
JP Simard 9ac13f078a
Update rules_xcodeproj to 0.12.3 (#4724)
https://github.com/buildbuddy-io/rules_xcodeproj/releases/tag/0.12.3
2023-01-26 21:24:21 -05:00
JP Simard c84124d8bc
Enable all tests when using rules_xcodeproj (#4723)
* Revert "Skip tests requiring runfiles support when testing with rules_xcodeproj (#4694)"

This reverts commit 040096a641.

* Enable all tests when using rules_xcodeproj

Turns out we don't need runfiles support
(https://github.com/buildbuddy-io/rules_xcodeproj/issues/828) since the
tests run outside the sandbox in Xcode, so we can get the path to these
files from the `BUILD_WORKSPACE_DIRECTORY` environment variable that
`bazel test` sets.
2023-01-26 21:18:05 -05:00
JP Simard 3907772163
Revert "[OSSCheck] Cache git repos (#4699)" (#4718)
This reverts commit fbd9f16955.
2023-01-23 17:05:15 +00:00
dependabot[bot] d98fd5386a
Bump activesupport from 6.1.6.1 to 6.1.7.1 (#4716)
Bumps [activesupport](https://github.com/rails/rails) from 6.1.6.1 to 6.1.7.1.
- [Release notes](https://github.com/rails/rails/releases)
- [Changelog](https://github.com/rails/rails/blob/v7.0.4.1/activesupport/CHANGELOG.md)
- [Commits](https://github.com/rails/rails/compare/v6.1.6.1...v6.1.7.1)

---
updated-dependencies:
- dependency-name: activesupport
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-21 09:18:21 +01:00
JP Simard 36c24c822c
Add "fixed releaser" to BCR config
So that the publish-to-bcr GitHub app uses my personal fork of
bazel-central-registry as documented in
https://github.com/bazel-contrib/publish-to-bcr/blob/main/templates/README.md#optional-configyml
2023-01-19 21:41:20 -05:00
Keith Smiley e1fdf17d1b
Add bazel BCR config (#4713) 2023-01-19 21:17:21 -05:00
Keith Smiley 23e4db3e5f
Add support for bzlmod (#4704) 2023-01-19 17:04:21 -05:00
JP Simard b4641e7e60
Update rules_xcodeproj to 0.12.2 (#4709)
https://github.com/buildbuddy-io/rules_xcodeproj/releases/tag/0.12.2
2023-01-19 15:36:47 -05:00
Danny Mösch 341db66f3c
Support Swift version 5.7.3 (#4711) 2023-01-19 15:35:41 -05:00
JP Simard c9791783ba
Update rules_xcodeproj to 0.12.0 (#4703)
This is the first release to fix running unit tests since 0.10.1.

It should include some performance improvements and bug fixes that will
benefit us.

https://github.com/buildbuddy-io/rules_xcodeproj/releases/tag/0.12.0
2023-01-18 16:14:55 +00:00
JP Simard dc228d57ac
Update dependencies (#4702) 2023-01-17 12:59:32 -05:00
JP Simard fbd9f16955
[OSSCheck] Cache git repos (#4699)
By maintaining persistent clones of the repos which are incrementally
synced instead of cloned from scratch on every run.
2023-01-17 12:07:48 -05:00
JP Simard d120f41181
[CI] Remove 'TSan Runs' job (#4701)
This frequently crashes and I don't think it's due to a real TSan race.

E.g. https://buildkite.com/swiftlint/swiftlint/builds/4912#0185c098-a803-4525-8df1-827d1c97ed01

```
swiftlint(373,0x1ecdd3a80) malloc: nano zone abandoned due to inability to preallocate reserved vm space.
Linting Swift files in current working directory
1 of 538 [                              ] ETA: 0s (13129 files/s)
PLEASE submit a bug report to https://bugs.llvm.org/ and include the crash backtrace.
ThreadSanitizer:DEADLYSIGNAL
==373==ERROR: ThreadSanitizer: SEGV on unknown address 0x000000000008 (pc 0x000102cbe380 bp 0x00016f50ee00 sp 0x00016f50edc0 T30753687)
==373==The signal is caused by a UNKNOWN memory access.
==373==Hint: address points to the zero page.
    #0 __tsan::ThreadClock::release(__tsan::DenseSlabAllocCache*, __tsan::SyncClock*) <null>:46801128 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x2a380)
    #1 __tsan::Release(__tsan::ThreadState*, unsigned long, unsigned long) <null>:46801128 (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x7456c)
    #2 swift::runJobInEstablishedExecutorContext(swift::Job*) <null>:46801128 (libswift_Concurrency.dylib:arm64e+0x40588)
    #3 swift_job_runImpl(swift::Job*, swift::ExecutorRef) <null>:46801128 (libswift_Concurrency.dylib:arm64e+0x41404)
    #4 _dispatch_root_queue_drain <null>:46801128 (libdispatch.dylib:arm64e+0x15f90)
    #5 _dispatch_worker_thread2 <null>:46801128 (libdispatch.dylib:arm64e+0x167bc)
    #6 _pthread_wqthread <null>:46801128 (libsystem_pthread.dylib:arm64e+0x30c0)
    #7 start_wqthread <null>:46801128 (libsystem_pthread.dylib:arm64e+0x1e1c)

==373==Register values:
 x[0] = 0x0000000102a53458   x[1] = 0x0000000000000538   x[2] = 0x0000000000000000   x[3] = 0x0000000000000000
 x[4] = 0x0000000000000001   x[5] = 0x0000000000000000   x[6] = 0x0095000004220122   x[7] = 0x0000000000000001
 x[8] = 0x0000000000000008   x[9] = 0x0000000000000000  x[10] = 0x0000000000000000  x[11] = 0x0000000000000000
x[12] = 0x0000000000000020  x[13] = 0x0000000110904040  x[14] = 0x0000000000000000  x[15] = 0x0000000106251910
x[16] = 0x0000000104190960  x[17] = 0x0000000000200018  x[18] = 0x0000000000000000  x[19] = 0x000000010f5d3488
x[20] = 0x0000000109ca03f0  x[21] = 0x0000000102a53458  x[22] = 0x0000000109ca03f0  x[23] = 0x0000000109cc0078
x[24] = 0x00040c0000fd77c4  x[25] = 0x0000010000000000  x[26] = 0x00000002287898f8  x[27] = 0x0000000000000000
x[28] = 0x000000016f50f0e0     fp = 0x000000016f50ee00     lr = 0x0000000102cbe360     sp = 0x000000016f50edc0
ThreadSanitizer can not provide additional info.
SUMMARY: ThreadSanitizer: SEGV (libclang_rt.tsan_osx_dynamic.dylib:arm64e+0x2a380) in __tsan::ThreadClock::release(__tsan::DenseSlabAllocCache*, __tsan::SyncClock*)+0x174
==373==ABORTING
```
2023-01-17 11:42:46 -05:00
JP Simard db6aea5d07
Disable remote cache for the "TSan Runs" CI job (#4700)
I suspect it might be the reason why it occasionally crashes.
2023-01-17 11:24:48 -05:00
JP Simard 876d8fc872
Remove `.swp` file that snuck in (#4697)
https://github.com/realm/SwiftLint/pull/4657
2023-01-17 08:23:53 -05:00
JP Simard 45c5f06754
Move changelog sections to the latest section (#4698)
These were introduced after 0.50.3 was released.
2023-01-17 13:16:12 +00:00
JP Simard 040096a641
Skip tests requiring runfiles support when testing with rules_xcodeproj (#4694)
rules_xcodeproj doesn't support runfiles yet:
https://github.com/buildbuddy-io/rules_xcodeproj/issues/828
2023-01-16 13:09:56 -05:00
JP Simard f91a2d5310
Revert rules_xcodeproj to 0.10.1 (#4693)
It looks like later versions can't run tests because of
https://github.com/buildbuddy-io/rules_xcodeproj/issues/1586.
2023-01-16 17:14:31 +00:00
Danny Mösch f8f2317bdb
Extract common plugin execution code to method (#4690) 2023-01-14 12:35:53 +01:00
Danny Mösch 82249f5ed4
Include recently added option into rule/cache description (#4688) 2023-01-13 17:37:29 -05:00
Danny Mösch 78c9ad6f33
Rephrase rule description (#4686)
"Can't" sounds too hard. The Swift compiler allows multiple enum cases with the same name. It's rather a recommendation.
2023-01-12 18:06:04 +01:00
Danny Mösch 693e504258
Use basic NSRegularExpression type with caching (#4683) 2023-01-11 20:28:16 +01:00
Danny Mösch b66dd13e2f
Ensure negative literals in initializers don't trigger `no_magic_numbers` (#4679) 2023-01-11 00:09:53 +01:00
kyounh12 5ec6112ba1
Interpret strings in `excluded` option of `*_name` rules as regex (#4655) 2023-01-10 23:29:04 +01:00
Danny Mösch 74dbd52add Fix/improve some rule descriptions 2023-01-10 22:29:13 +01:00
Danny Mösch cc44c989b7 Check every used internal violation reason for a pending period 2023-01-10 22:29:13 +01:00
Danny Mösch e64e82d80d Get rid of periods finishing rule descriptions 2023-01-10 22:29:13 +01:00
dependabot[bot] ea8b17dd9c
Bump git from 1.12.0 to 1.13.0 (#4678)
Bumps [git](https://github.com/ruby-git/ruby-git) from 1.12.0 to 1.13.0.
- [Release notes](https://github.com/ruby-git/ruby-git/releases)
- [Changelog](https://github.com/ruby-git/ruby-git/blob/master/CHANGELOG.md)
- [Commits](https://github.com/ruby-git/ruby-git/compare/v1.12.0...v1.13.0)

---
updated-dependencies:
- dependency-name: git
  dependency-type: indirect
...

Signed-off-by: dependabot[bot] <support@github.com>

Signed-off-by: dependabot[bot] <support@github.com>
Co-authored-by: dependabot[bot] <49699333+dependabot[bot]@users.noreply.github.com>
2023-01-09 17:29:07 -05:00
Danny Mösch 672e19651f Use configuration raw name expected when reading the configuration 2023-01-08 15:05:43 +01:00
Danny Mösch 67eb330f1c Add key to severity configuration as a hint for the correct YAML syntax 2023-01-08 15:05:43 +01:00
JP Simard e6d4ad653c
Pin SwiftSyntax to a tagged version (#4674)
Specifically this tag:
https://github.com/apple/swift-syntax/releases/tag/0.50800.0-SNAPSHOT-2022-12-29-a

This should make SwiftPM happier about the stability of the pinned
version.

Also switch the CI job to run on macOS to avoid adding Linux-only deps
to the `Package.resolved` file (e.g. CryptoSwift).
2023-01-06 13:10:59 -05:00
JP Simard 100b3eec2a
Update Docker CI job to use `actions/checkout@v3` (#4675) 2023-01-06 18:04:50 +00:00
github-actions[bot] bcfc2c4b6c
Update SwiftSyntax (#4673)
* Exclude `Documentation.docc` when building SwiftSyntax
* Remove references to `.stringInterpolationAnchor`

Co-authored-by: JP Simard <jp@jpsim.com>
2023-01-06 11:12:17 -05:00
Ethan Wong 68dc0f58d2
List Analyzer rules in an independent section in the rule directory (#4664) 2023-01-03 07:18:25 +01:00
Danny Mösch d6ff2a7f37
Rewrite `explicit_type_interface` rule with SwiftSyntax fixing a false-positive (#4638) 2023-01-01 23:23:50 +01:00
Danny Mösch 64d9619a8a
Report violations in all `<scope>_length` rules when error < warning threshold (#4647) 2023-01-01 23:15:36 +01:00
github-actions[bot] 470d471e51
Update SwiftSyntax (#4659)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-12-26 09:37:51 -05:00
Danny Mösch 5ff40867b3
Apply consistent capitalization to rule names (#3938) 2022-12-26 09:32:36 +01:00
Martin Redington 58611e6718
Ignore compiler directives in `opening_brace` rule (#4658) 2022-12-25 18:46:16 +01:00
Danny Mösch c740da48d5
Deprecate `inert_defer` rule in favor of the Swift compiler warning (#4618) 2022-12-23 04:07:41 -05:00
Jimmy Arts eda0d92f44
Make forceExclude work with directly specified files (#4609) 2022-12-22 21:15:08 +01:00
Benedek Kozma d76daf5f62
Deprecate `unused_capture_list` rule in favor of the Swift compiler warning (#4656) 2022-12-22 10:33:44 -05:00
JP Simard 1a39194f65
Update rules_xcodeproj to 0.11.0 (#4653)
https://github.com/buildbuddy-io/rules_xcodeproj/releases/tag/0.11.0
2022-12-21 18:07:55 -05:00
JP Simard 0e53e19cb1
[CI] Increase timeout for `analyze` CI job to 60 minutes (#4654)
I recently moved CI machines over to use Xcode 14.2 and it's possible
that running SwiftLint analyzer rules now take longer.
2022-12-21 17:37:44 -05:00
JP Simard 3cba460f9a
Update rules_apple to 2.0.0 (#4652)
https://github.com/bazelbuild/rules_apple/releases/tag/2.0.0
2022-12-21 16:08:44 -05:00
Julio Carrettoni d3928cb8e8
Update docker_test to swift 5.7 (#4625) 2022-12-19 19:12:44 -03:00
JP Simard a6c3cec8c9
Update to Bazel 6.0.0 (#4651)
https://www.buildbuddy.io/blog/whats-new-in-bazel-6-0/
2022-12-19 15:12:53 -05:00
Marcelo Fabri 7bd8362dae
Fix false positive on `private_subject` (#4646)
Fixes #4643
2022-12-19 13:31:34 -03:00
JP Simard e1cddb710d
Remove remote bazel cache steps from CI instructions (#4650)
Moved over to BuildBuddy.
2022-12-19 11:02:04 -05:00
github-actions[bot] 4a6387da67
Update SwiftSyntax (#4649)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-12-19 09:22:53 -05:00
Danny Mösch 83ee9a89dd
Add Swift version 5.7.2 (#4648) 2022-12-17 04:18:21 -05:00
David Steinacher 540003e2a1
Add support for new Quick APIs `aroundEach` and `justBeforeEach` (#4627) 2022-12-15 23:29:02 +01:00
Julio Carrettoni 3745704c03
Add rule for single space after period on comments (#4624) 2022-12-15 23:27:33 +01:00
github-actions[bot] 33fa42becb
Update SwiftSyntax (#4644)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-12-14 11:17:34 -05:00
github-actions[bot] 25c3fb161d
Update SwiftSyntax (#4639)
* `ObjcKeyPathExprSyntax` -> `MacroExpansionExprSyntax`
* Fix `#selector()` and add a regression test

Co-authored-by: JP Simard <jp@jpsim.com>
2022-12-14 10:52:47 -05:00
JP Simard 53ee4a955d
Fix `make release`
`make package` has a dependency on `make clean` so this was removing
the bazel release artifacts.
2022-12-09 14:42:20 -05:00
JP Simard 40fb3796cb
Add empty changelog section 2022-12-09 14:41:42 -05:00
JP Simard a876e860ee
release 0.50.3 2022-12-09 14:12:02 -05:00
JP Simard 4c406459bd
Set first changelog entry header to `Main` again 2022-12-09 14:10:55 -05:00
JP Simard 4ddeb22b6d
Fix `Package.swift` 2022-12-09 14:08:08 -05:00
JP Simard ff06e0ed00
release 0.50.2 2022-12-09 13:49:24 -05:00
github-actions[bot] 6734a64554
Update SwiftSyntax (#4636)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-12-09 13:25:09 -05:00
JP Simard 73a64d674c
Fix rewrite issue with `comma` rule (#4635) 2022-12-09 12:56:50 -05:00
github-actions[bot] 7f8eb9de77
Update SwiftSyntax (#4629)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-12-07 17:44:23 -05:00
Danny Mösch 609418f702
Skip `defer` statements in `#if` block if it is not itself the last statement (#4617) 2022-12-06 02:22:03 -05:00
Danny Mösch 4adabd8e4b
Print a warning for Analyzer rules misplaced in the configuration (#4620) 2022-12-05 18:03:54 -05:00
Danny Mösch 93d0d8fa7f
Fix configuration parsing error in `unused_declaration` rule (#4619) 2022-12-05 23:11:19 +01:00
JP Simard 4dfef014ab
Build with `--swiftcopt=-warnings-as-errors` when using Bazel (#4622) 2022-12-05 11:33:02 -05:00
JP Simard 95d56e4130
Use `raw` in SwiftSyntaxBuilder string interpolation (#4623)
Fixing build warnings to account for
https://github.com/apple/swift-syntax/pull/1090.
2022-12-05 11:26:04 -05:00
JP Simard 7f9dd69c62
Move changelog entries to correct sections 2022-12-05 10:33:29 -05:00
github-actions[bot] ac44fe23b6
Update SwiftSyntax (#4621)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-12-05 08:40:45 -05:00
Marcelo Fabri 60128ab196
Don’t trigger shorthand_operator inside operator declaration (#4613)
Fixes #4611
2022-12-02 02:00:44 -05:00
Martin Redington 7a8b2d1dab
Add `test_parent_class` option to more test rules (#4262) 2022-12-01 18:56:13 +01:00
Ryan Aveo 92304cdd98
Fix building tests in Xcode (#4607)
Co-authored-by: JP Simard <jp@jpsim.com>
2022-11-30 10:33:33 -05:00
Tony Arnold ab143685a4
Use a binary target for the build tool plugin (#4603)
* Use a binary target for the build tool plugin

* Merge `push_version` and `release` make commands

Instead of running `make push_version "0.2.0: Tumble Dry"` and then
`make release`, now run `make release "0.2.0: Tumble Dry"`, which will
build the release artifacts and update/push the new version to GitHub.

This allows the artifacts to use the new version, update the artifact
bundle checksum in the package manifest, then tag the release.

The Releasing.md instructions were updated to reflect this new workflow.

* Add `SwiftLintSourcePlugin` source plugin for SwiftPM

* Add changelog entry

* Remove SwiftLintSourcePlugin for now

* Build from Source on Linux

* Use a lower-level method of checking if a file is accessible

This shouldn’t trigger sandbox violations, I hope…

* Prevent an infinite recursion of the filesystem root

* Remove unnecessary logging

* Quieten the output so that Xcode only prints violations

* Break up comment to avoid line length warning

* Fix capitalization of Glibc import

Co-authored-by: JP Simard <jp@jpsim.com>
2022-11-29 18:10:47 -05:00
github-actions[bot] 9cb1069090
Update SwiftSyntax (#4605)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-11-29 15:30:56 -05:00
JP Simard e405d3d583
Fix `update-swift-syntax.sh` script (#4604) 2022-11-29 15:07:37 -05:00
Steffen Matthischke 97ba82d740
Fix empty_enum_arguments false positive when called expression is an identifier (#4600) 2022-11-28 17:20:34 -05:00
JP Simard d8d5e5bb43
Add empty changelog section 2022-11-28 09:20:39 -05:00
JP Simard 28a4aa2195
release 0.50.1 2022-11-25 11:38:16 -05:00
JP Simard 28e2a1b022
Fix configuration description for some vertical whitespace rules (#4594)
Before: `N/A`
After: `warning, only_enforce_before_trivial_lines: false`
2022-11-25 11:18:41 -05:00
JP Simard c1c5e5f722
Add test cases for https://github.com/realm/SwiftLint/issues/4565 (#4593) 2022-11-25 11:12:48 -05:00
JP Simard 60ad710b7f
Fix `type_name` regression when using backticks (#4592)
Fixes https://github.com/realm/SwiftLint/issues/4571
2022-11-25 10:57:05 -05:00
JP Simard 5a7d3ade02
Lint Plugins directory (#4591) 2022-11-25 15:51:09 +00:00
JP Simard 12c8bb369e
Fix false positives in `unused_closure_parameter` when using backticks (#4590)
Fixes https://github.com/realm/SwiftLint/issues/4588
2022-11-25 07:29:31 -05:00
Tony Arnold f088bbde15
Only lint Swift source files within the provided target (#4406)
* Only lint Swift source files within the provided target

* Restore plugin display name to “SwiftLint”

* Xcode targets don’t always conform to the SourceModuleTarget

Use a “baser” way to get at the Swift sources of the current target

* Use the cache path as the outputFiles

This prevents the warning we’re seeing in Xcode on every build, but shouldn’t interfere with the re-run logic

* Look for configuration files manually in the project directory and parents

* Check if the file is readable, not just that it exists

* Move the Path helper into a separate file

* Fix a wayward apostrophe

Signed-off-by: Tony Arnold <tony@thecocoabots.com>

* Add an entry to the CHANGELOG

Signed-off-by: Tony Arnold <tony@thecocoabots.com>

Signed-off-by: Tony Arnold <tony@thecocoabots.com>
2022-11-24 18:38:41 -05:00
JP Simard e9c1128b86
Move SwiftPM CI job to Buildkite (#4589)
We have 6 CI machines now and these should run faster on bare metal
machines with lots of stuff already cached, like starting with an
existing git repo.
2022-11-24 17:58:09 +00:00
JP Simard 4ccf9eebb4
Fix line count calculation for multiline string literals (#4587)
Fixes https://github.com/realm/SwiftLint/issues/4585

Update body length rule thresholds for SwiftLint
2022-11-24 16:40:51 +00:00
JP Simard f6de471b5f
Fix false positives in `closure_spacing` (#4584)
Fixes https://github.com/realm/SwiftLint/issues/4582
2022-11-24 10:30:40 -05:00
JP Simard 0f68a0a0cb
Fix `update-swift-syntax.sh` (#4586)
I forgot to update this in https://github.com/realm/SwiftLint/pull/4570
2022-11-24 10:26:51 -05:00
JP Simard ac21584224
Fix `minimum_fraction_length` handling in `number_separator` (#4583)
Fixes https://github.com/realm/SwiftLint/issues/4576

Before it would apply the `minimum_length` parameter to the
`minimal_fractional_length` setting if the latter wasn't set.
2022-11-24 14:19:20 +00:00
Steffen Matthischke 907c80f84c
Fix lower_acl_than_parent false positives when the nominal parent is an extension (#4575) 2022-11-24 08:08:23 -05:00
JP Simard fd502b4526
Add new `local_doc_comment` rule (#4581)
Moving the validation of doc comments in local scopes out of
`orphaned_doc_comment` and into a new opt-in `local_doc_comment` rule.

Addresses https://github.com/realm/SwiftLint/issues/4573.
2022-11-23 17:50:18 -05:00
JP Simard a6c4ea9614
Makefile: use bazel in more places (#4580)
Previously, it took a very long time to run `make release` since it had
to clean build everything.

With bazel, I'm more confident in incremental builds being solid, so it
now just takes a few seconds if there's anything cached, which is often
the case.
2022-11-23 21:41:28 +00:00
JP Simard 571ad33ceb
Add `universal_swiftlint` universal macOS binary (#4579)
With architectures for x86_64 and arm64 for portable distribution.
2022-11-23 11:33:38 -05:00
JP Simard 6af9175386
Update swift-argument-parser to 1.2.0 (#4572) 2022-11-21 15:33:00 +00:00
JP Simard fe4ec3521e
Update `Package.swift` to `swift-tools-version:5.7` (#4570)
We already require Swift 5.7 as of SwiftLint 0.50.0.
2022-11-21 15:14:25 +00:00
JP Simard 1dbde3d9c3
Update SourceKitten to 0.33.1 (#4569)
To make SwiftPM happy about the package being set to a stable release.
2022-11-21 09:52:23 -05:00
github-actions[bot] cc49e82348
Update SwiftSyntax (#4567)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-11-21 09:15:01 -05:00
JP Simard 4408250920
Fix building with `swift build -c release` (#4563)
Which previously tried to build the SwiftLintTestHelpers module,
which is a test-only module.

This should fix building with precommit and Mint, both of which assume
you can build the CLI targets in a Swift package with the
`swift build -c release` command.
2022-11-20 20:59:39 +00:00
JP Simard 85e59c706a
Disable `prefer_self_in_static_references` in SwiftLint (#4557)
I find this rule to not help readability for this project at least,
where `Version(value: "0.50.0")` is a lot more descriptive than
`Self(value: "0.50.0")`.
2022-11-19 09:23:55 -05:00
JP Simard cdd891a4a2
release 0.50.0 2022-11-18 14:43:44 -05:00
JP Simard e5b1944ae6
Reorder changelog entries 2022-11-18 14:43:21 -05:00
github-actions[bot] cd291b043a
Update SwiftSyntax (#4556) 2022-11-18 14:41:07 -05:00
JP Simard 8cdbb50cbf
Update rules_xcodeproj 2022-11-16 16:22:25 -05:00
JP Simard ba8899714d
Reduce visibility of custom rules (#4553) 2022-11-16 16:14:50 -05:00
JP Simard 4f652a68e7
Move XCTestHelpers to TestCaseAccessibilityRule.swift (#4552)
it's only used in that file
2022-11-16 14:31:00 -05:00
JP Simard 953ee620f7
Refactor ExecutableInfo (#4551)
To improve how it renders in jazzy-generated docs.
2022-11-16 14:07:39 -05:00
JP Simard 18f9a0e3a6
Write some docs for internal APIs (#4550)
These should be useful when writing rules, but also may become public
API in the future.
2022-11-16 13:58:55 -05:00
JP Simard 0362cd07b8
Fix jazzy CodeClimateReporter capitalization (#4549) 2022-11-16 13:16:05 -05:00
github-actions[bot] 9d2ec3dc8d
Update SwiftSyntax (#4547)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-11-13 19:35:15 -05:00
JP Simard 04d1184904
Remove structure cache (#4541) 2022-11-10 14:53:16 -05:00
JP Simard 4bd7da32ea
Reduce visibility of rules to be `internal` (#4533)
There's no reason to expose these publicly and this will make it nicer
to move to a new module outside of the core SwiftLint functionality.
2022-11-09 11:01:26 -05:00
JP Simard 901f05cd4f
Update rules_apple to 1.1.3 (#4537) 2022-11-09 10:59:52 -05:00
JP Simard ca0813c605
Update Bazel to 5.3.2 (#4536)
Latest stable release: https://github.com/bazelbuild/bazel/releases/tag/5.3.2
Run OSSCheck if `.bazelversion` changes
2022-11-09 10:47:55 -05:00
github-actions[bot] ac7833aaf4
Update SwiftSyntax (#4535)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-11-08 23:01:13 -05:00
github-actions[bot] 5fceb213d9
Update SwiftSyntax (#4529)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-11-08 10:24:02 -05:00
JP Simard 2928dab4ec
Fix running update-swift-syntax.sh locally 2022-11-08 10:05:45 -05:00
JP Simard 0c828e35fb
Improve automated Swift Syntax update PR messages 2022-11-08 09:52:20 -05:00
Danny Mösch 3864ecc12f
Fix indentation in error message (#4528) 2022-11-07 14:47:14 -05:00
github-actions[bot] fe141cd3b6
Update SwiftSyntax (#4527)
Diff: e1f771ea8a...4fa6e6b0ff
2022-11-07 10:42:36 -05:00
Marcelo Fabri eaf7db1250
Rewrite `object_literal` rule with SwiftSyntax (#4525) 2022-11-06 20:42:00 -08:00
Marcelo Fabri 6c7e2107ae
Rewrite `nslocalizedstring_key` with SwiftSyntax (#4523) 2022-11-06 19:12:53 -08:00
Danny Mösch 1edef5ebdc
Trigger `prefer_self_in_static_references` rule on constructor calls (#4519) 2022-11-06 14:55:28 +01:00
Danny Mösch 7e8a3ae2ac
Introduce `onlyElement` property on `Collection` (#4518) 2022-11-06 07:37:44 -05:00
Marcelo Fabri 45ac3dcdff
Rewrite `required_enum_case` with SwiftSyntax (#4521) 2022-11-06 01:26:11 -08:00
Marcelo Fabri 1c675bc993
Rewrite `balanced_xctest_lifecycle` with SwiftSyntax (#4520) 2022-11-06 00:05:32 -07:00
Marcelo Fabri fc0c0c7f5b
Rewrite `pattern_matching_keywords` with SwiftSyntax (#4490) 2022-11-05 23:16:59 -07:00
Marcelo Fabri 22522254df
Validate shorthand option binding in `self_binding` rule (#4514)
* Validate shorthand option binding in `self_binding` rule

* Fix CHANGELOG
2022-11-05 21:32:09 -07:00
Danny Mösch 9a706ba371
Trigger `prefer_self_in_static_references` rule in computed property blocks (#4517) 2022-11-06 00:19:48 +01:00
Danny Mösch d4ea19e434
Trigger `prefer_self_in_static_references` rule on key paths (#4516) 2022-11-05 13:39:27 +01:00
Danny Mösch eb3d502ad7
Rephrase rule description letting it no longer refer to classes only (#4515) 2022-11-05 07:13:24 -04:00
Danny Mösch 65874dc40f
Rewrite `prefer_self_in_static_references` with SwiftSyntax (#4504) 2022-11-05 11:46:33 +01:00
JP Simard 759408fdb5
Update SwiftSyntax (#4506)
This update pulls in https://github.com/apple/swift-syntax/pull/1050,
which fixes a memory leak, reducing memory usage by up to 3x and
improves lint times by 20%-50% in my tests.
2022-11-04 06:05:38 -04:00
Craig Siemens ec5124d939
Rewrite `multiline_arguments_brackets` with SwiftSyntax (#4512)
Fix false-positives when a function call has a single line closure.
2022-11-03 21:57:27 -04:00
JP Simard 05e06d1945
Allow passing a commit to `update-swift-syntax.sh` (#4509)
E.g.

```console
$ ./tools/update-swift-syntax.sh e19c5f2909127ce4537d6f8981919aba4645ce4e
```
2022-11-03 16:05:09 -04:00
John Szumski 6fcdd8077f
Adds NSError to the list of types in discouraged_direct_init. (#4508) 2022-11-03 13:52:30 -04:00
Danny Mösch 32152646f3
Use `Self` in constructor calls (#4505) 2022-11-02 22:50:22 +00:00
JP Simard e97119efec
Migrate `orphaned_doc_comment` to use SwiftSyntax classifications (#4461) 2022-11-02 15:55:47 +00:00
JP Simard fce8283843
Fix & clean up Dockerfile (#4502)
We no longer need `_InternalSwiftSyntaxParser`, but we do need
`libswiftCore.so`.

Fixes https://github.com/realm/SwiftLint/issues/4501

Add a more useful validation test at the end of the Dockerfile.
In addition to running `swiftlint version`, also actually run the linter
which will exercise the SourceKit codepaths.
2022-11-02 10:28:55 -04:00
JP Simard f1f6a3c4a0
Add `ByteSourceRange.toSourceKittenByteRange()` helper (#4500)
This will be useful for other rules too.
2022-11-01 14:13:29 -04:00
César Pinto Castillo c56e19a0c2
Fix SwiftLint support on Xcode Cloud (#4485)
Fixes #4484 by checking the process environment for all the expected
environment variables set by Xcode Cloud.
2022-11-01 13:00:50 -04:00
JP Simard 3800ad32bf
Sort list of rules migrated to SwiftSyntax in changelog (#4499) 2022-10-31 13:07:53 -04:00
JP Simard 79e3c8bd4a
Add special handling for https://github.com/apple/swift/issues/61817 (#4498) 2022-10-31 09:58:36 -04:00
github-actions[bot] 6ad4a704f5
Update SwiftSyntax (#4497) 2022-10-31 09:49:40 -04:00
Marcelo Fabri 20bfe264f5
Rewrite `overridden_super_call` and `prohibited_super_call` with SwiftSyntax (#4493)
* Rewrite `overridden_super_call` with SwiftSyntax

* Rewrite `prohibited_super_call` too
2022-10-30 20:28:25 -07:00
Marcelo Fabri 697eaa73dd
Rewrite `private_subject` rule with SwiftSyntax (#4495) 2022-10-30 18:41:57 -07:00
Marcelo Fabri 5144cccb57
Revert "[OSSCheck] Cache repos (#4456) (#4477)" (#4494)
This reverts commit 6b5352feac.
2022-10-30 17:46:53 -07:00
Marcelo Fabri 98e9c2f1f4
Fix SwiftVersionTests when using Xcode 14.2 (#4492) 2022-10-30 19:50:49 -04:00
Henry 63c043664d
Add new `no_magic_numbers` rule (#4265) 2022-10-30 15:28:20 +01:00
JP Simard a18436d4d6
Rewrite `optional_enum_case_matching` with SwiftSyntax (#4488) 2022-10-28 19:30:33 +00:00
JP Simard a5c44605af
Update to rules_xcodeproj 0.10.0 (#4486)
Mostly has optimizations: https://github.com/buildbuddy-io/rules_xcodeproj/releases/tag/0.10.0
2022-10-28 14:17:03 -04:00
JP Simard b7f621c239
Make `update-swift-syntax.sh` script portable (#4487) 2022-10-28 14:16:48 -04:00
JP Simard d551cb8c16
Update SwiftSyntax (#4480)
Moves syntax classifications to a new IDEUtils module.
2022-10-28 09:23:06 -04:00
JP Simard 1b59a3f168
Cache location converters (#4481)
This is 2%-12% faster according to OSSCheck
2022-10-26 12:37:16 -04:00
JP Simard 6b5352feac
[OSSCheck] Cache repos (#4456) (#4477)
This should speed up OSSCheck runs considerably.

In my local testing, cloning repos goes from 77s to 29s on a 100 Mbps
connection.
2022-10-26 10:58:23 -04:00
JP Simard 9a305dd2e6
Update CI setup instructions (#4479) 2022-10-26 10:25:06 -04:00
github-actions[bot] 7738f0c0a5
Update SwiftSyntax (#4476)
Co-authored-by: jpsim <jpsim@users.noreply.github.com>
2022-10-24 17:10:17 -04:00
JP Simard 374130bb56
Update SwiftSyntax on a weekly scheduled CI job (#4475) 2022-10-24 16:37:29 -04:00
Marcelo Fabri d37465ad37
Rewrite `vertical_parameter_alignment` with SwiftSyntax (#4466)
* Rewrite `vertical_parameter_alignment` rule with SwiftSyntax

* Validate inits
2022-10-24 12:56:48 -07:00
Marcelo Fabri 50c85ef5c1
Rewrite `explicit_top_level_acl` with SwiftSyntax (#4450) 2022-10-24 11:31:12 -07:00
Marcelo Fabri fa6db3cca7
Rewrite `collection_alignment` rule with SwiftSyntax (#4472) 2022-10-24 10:51:31 -07:00
JP Simard 96925d480e
Revert "[OSSCheck] Cache repos (#4456)" (#4473)
This reverts commit f0f46a45eb.

This might need more error handling / recovery:
https://github.com/realm/SwiftLint/pull/4456#issuecomment-1288792588
2022-10-24 06:26:52 -04:00
Marcelo Fabri 31ea58381c
Add example for #3558 (#4471)
Fixes #3558
2022-10-24 06:14:30 -04:00
Marcelo Fabri 95838aa230
Add example for #2008 (#4470)
Fixes #2008
2022-10-24 05:32:56 -04:00
Marcelo Fabri f8d505a225
Add example for #3945 (#4469)
Fixes #3945
2022-10-24 09:23:37 +00:00
Marcelo Fabri 7a8de1c966
Add example for #2782 (#4468)
Fixes #2782
2022-10-24 09:22:31 +00:00
Marcelo Fabri 7a3d0d55a0
Add example for #3022 (#4467)
Fixes #3022
2022-10-24 08:56:23 +00:00
Marcelo Fabri 1c6b6c67e0
Rewrite `duplicated_key_in_dictionary_literal` with SwiftSyntax (#4396) 2022-10-23 23:11:17 -07:00
Marcelo Fabri 87bebb6744
Rewrite `test_case_accessibility` with SwiftSyntax (#4446) 2022-10-23 17:39:45 -07:00
Marcelo Fabri 7f5b189c1f
Rewrite `override_in_extension` with SwiftSyntax (#4463) 2022-10-23 17:38:40 -07:00
Marcelo Fabri c1f2b615e2
Rewrite `redundant_optional_initialization` with SwiftSyntax (#4409) 2022-10-23 20:30:29 -04:00
Marcelo Fabri 5d1906456b
Rewrite `xct_specific_matcher` with SwiftSyntax (#4462) 2022-10-23 19:17:29 -04:00
Marcelo Fabri 53752f58ad
Rewrite `redundant_set_access_control` with SwiftSyntax (#4395) 2022-10-23 15:30:01 -07:00
Marcelo Fabri c98ffcc940
Rewrite `convenience_type` rule with SwiftSyntax (#4452) 2022-10-23 15:28:55 -07:00
Marcelo Fabri 351c8be2e0
Rewrite `return_arrow_whitespace` with SwiftSyntax (#4413) 2022-10-23 15:06:14 -07:00
JP Simard cb79584c7d
Migrate `comment_spacing` to use SwiftSyntax classifications (#4460) 2022-10-23 17:39:38 -04:00
Marcelo Fabri def404259f
Add more repos to oss-check (#4453) 2022-10-23 14:21:02 -07:00
JP Simard de7996f5c7
Rewrite `discarded_notification_center_observer` with SwiftSyntax (#4459) 2022-10-23 20:35:09 +00:00
JP Simard b4f45aa55e
Move TSan Runs CI job to Buildkite (#4457)
Now that we have more Buildkite machines, these should be faster than
running on Azure Pipelines.

Also remove the `//bazel:xcode_config` configurations since they should
no longer be in use.
2022-10-23 13:05:28 -04:00
JP Simard f0f46a45eb
[OSSCheck] Cache repos (#4456)
This should speed up OSSCheck runs considerably.

In my local testing, cloning repos goes from 77s to 29s on a 100 Mbps
connection.
2022-10-23 12:20:11 -04:00
JP Simard 1ee5154687
Update SwiftSyntax to `fa7ff05` (#4455)
There's a new SwiftParserDiagnostics module and the rewriter visit
function signatures changed.
2022-10-23 11:56:22 -04:00
JP Simard 401d0f7929
Update SwiftSyntax to `60c7037` (#4454)
This has changes to how comments are associated to nodes.

See https://github.com/apple/swift-syntax/pull/985
2022-10-23 11:15:27 -04:00
Marcelo Fabri 25a04e8eeb
Migrate `inclusive_language` rule to SwiftSyntax (#4390) 2022-10-23 14:00:40 +00:00
Marcelo Fabri 0bd9e4e65f
Rewrite `unused_optional_binding` with SwiftSyntax (#4449) 2022-10-23 06:13:54 -07:00
Marcelo Fabri 60054da0f7
Rewrite `prefer_self_type_over_type_of_self` with SwiftSyntax (#4451) 2022-10-23 06:06:16 -07:00
Marcelo Fabri 35fcf55198
Rewrite `reduce_into` rule with SwiftSyntax (#4447) 2022-10-23 07:58:48 -04:00
Marcelo Fabri cee4af098f
Migrate `prefer_zero_over_explicit_init` to SwiftSyntax (#4448) 2022-10-23 04:53:00 -07:00
Marcelo Fabri 9c8708bc01
Extract common SwiftSyntax extensions (#4445) 2022-10-23 00:54:18 -07:00
Marcelo Fabri 7b1099a33d
Rewrite `prefixed_toplevel_constant` with SwiftSyntax (#4425) 2022-10-21 13:11:40 -07:00
JP Simard 359699cea7
Report memory usage when `--benchmark` is specified (#4442)
With the memory increase related to
https://github.com/apple/swift-syntax/issues/921,
I wanted a way to quickly know how much memory was being used by
SwiftLint.

Here's how much memory it took to lint SwiftLint itself at various
versions in the last few weeks:

* 0.49.1: 223 MB
* 0.50.0-rc.1: 356 MB
* 0.50.0-rc.2: 444 MB
* main (458916174): 464 MB

```console
$ swiftlint --progress --benchmark
Linting Swift files in current working directory
529 of 529 [==============================] ETA: 0s (121 files/s)
Done linting! Found 0 violations, 0 serious in 529 files.
Memory used: 464 MB
$ swiftlint --progress --benchmark
Linting Swift files in current working directory
529 of 529 [==============================] ETA: 0s (16220 files/s)
Done linting! Found 0 violations, 0 serious in 529 files.
Memory used: 55.8 MB
```
2022-10-21 19:04:43 +00:00
JP Simard 4589161742
Rewrite `redundant_objc_attribute` with SwiftSyntax (#4441) 2022-10-21 18:28:21 +00:00
JP Simard 5af8e3dd68
Rewrite `lower_acl_than_parent` with SwiftSyntax (#4432)
And fix violations in SwiftLint.
2022-10-21 09:31:10 -04:00
Marcelo Fabri 1fb04902c0
Rewrite `multiline_parameters` rule with SwiftSyntax (#4438) 2022-10-21 09:08:27 +00:00
Marcelo Fabri 0dfc7a85b5
Rewrite `required_deinit` rule with SwiftSyntax (#4439) 2022-10-21 08:33:54 +00:00
Marcelo Fabri a4fbec355c
Validate protocols in `type_name` rule (#4433)
Fixes #4430
2022-10-21 00:11:18 -07:00
Marcelo Fabri 8f3a23e1a9
[oss-check] Add Pocket-Casts (#4436) 2022-10-20 19:04:43 -07:00
Marcelo Fabri ead4ca9b56
Revert "Add Automattic/pocket-casts-ios to oss-check (#4434)" (#4435)
This reverts commit 2d76e43068.
2022-10-20 18:54:10 -07:00
Marcelo Fabri 9f4b2c37ba
Rewrite `for_where`, adding `allow_for_as_filter` config (#4154) 2022-10-20 18:47:31 -07:00
Marcelo Fabri f509998ec1
Rewrite `empty_count` with SwiftSyntax (#4426) 2022-10-20 16:52:30 -07:00
Marcelo Fabri 2d76e43068
Add Automattic/pocket-casts-ios to oss-check (#4434) 2022-10-20 16:51:44 -07:00
JP Simard e84430f222
Update SwiftSyntax (#4431) 2022-10-20 13:16:00 -04:00
Marcelo Fabri 469a3aa87a
Rewrite `type_name` rule with SwiftSyntax (#4428) 2022-10-20 09:22:39 -07:00
Marcelo Fabri 59ad84f6e9
Add ns_number_init_as_function_reference rule (#4414) 2022-10-20 09:15:17 -07:00
Marcelo Fabri fd722892e5
Rewrite `notification_center_detachment` with SwiftSyntax (#4422) 2022-10-20 09:14:51 -07:00
Marcelo Fabri c46fd55711
Migrate `file_name` rule to SwiftSyntax (#4423) 2022-10-20 09:14:08 -07:00
Marcelo Fabri 2929e35183
Rewrite `single_test_class` rule with SwiftSyntax (#4424) 2022-10-20 09:13:23 -07:00
Marcelo Fabri 533917dced
Rewrite `raw_value_for_camel_cased_codable_enum` with SwiftSyntax (#4427) 2022-10-20 08:23:32 -07:00
JP Simard 25c9d34f5a
Update SwiftSyntax (#4429) 2022-10-20 09:21:14 -04:00
Marcelo Fabri d11dc2b321
Add changelog for https://github.com/realm/SwiftLint/pull/4421 2022-10-19 23:24:16 -07:00
Marcelo Fabri 62c8985a3e
Make `Quick*` rules more permissive (#4421)
Fixes #4420
2022-10-19 16:08:35 -07:00
JP Simard 230688848e
Update CI docs (#4419)
To reflect the need to install `bazelisk`.
2022-10-19 14:14:47 +00:00
JP Simard eb8971a933
Update CI docs (#4418)
Needs the `HOME` environment variable to bet set with some machines.
2022-10-19 09:10:36 -04:00
JP Simard 016831d471
Update CI machine setup instructions (#4416) 2022-10-19 07:49:18 -04:00
Marcelo Fabri 93b016bca3
Migrate `unavailable_function` to SwiftSyntax (#4387) 2022-10-18 20:19:27 -07:00
Marcelo Fabri bf716d7089
Rewrite `unused_capture_list` with SwiftSyntax (#4412) 2022-10-18 20:16:39 -07:00
Marcelo Fabri d3274565a1
Migrate `compiler_protocol_init` rule to SwiftSyntax (#4392) 2022-10-18 20:14:00 -07:00
Marcelo Fabri 04d7ce05ca
Rewrite `joined_default_parameter` with SwiftSyntax (#4411) 2022-10-18 20:12:56 -07:00
Marcelo Fabri bda8487ed2
Rewrite `prefer_nimble` rule with SwiftSyntax (#4410) 2022-10-18 01:42:08 -07:00
Danny Mösch a8bc9e9b42
Let more rules rely on SwiftSyntaxRule's defaults (#4401) 2022-10-18 02:23:39 -04:00
Marcelo Fabri 286e59a0e6
Fix false positive in `nsobject_prefer_isequal` rule (#4408)
Fixes #4404
2022-10-17 22:26:37 -07:00
Marcelo Fabri db910946dc
Simplift `DiscouragedNoneNameRule` implementation (#4407) 2022-10-18 04:43:40 +00:00
JP Simard 1837ae59cb
Rewrite `legacy_objc_type` with SwiftSyntax (#4372) 2022-10-17 17:29:09 -04:00
Marcelo Fabri 03848ce583
Make `DiscouragedNoneNameRule` opt-in again (#4403)
I mistakenly made it enable by default when rewriting it
2022-10-17 17:01:42 -04:00
JP Simard e9f2248072
Remove dead code in `DiscouragedDirectInitRule.swift` (#4402) 2022-10-17 18:25:02 +00:00
Marcelo Fabri 9a9629c1e8
Rewrite `discouraged_direct_init` with SwiftSyntax (#4397) 2022-10-17 10:47:46 -07:00
JP Simard 1b86aa7eeb
Simplify `SwitchCaseAlignmentRule` (#4399)
By using `ReasonedRuleViolation`, `SwiftSyntaxRule` and `makeVisitor()`.
2022-10-17 10:59:07 -04:00
JP Simard 8ec6e0eefc
Update SwiftSyntax (#4398)
https://github.com/apple/swift-syntax/pull/938 was impacting some
corrections in Lyft's codebase.
2022-10-17 09:42:02 -04:00
Marcelo Fabri 937228d1ba
Add `function_parameter_count` to CHANGELOG 2022-10-17 00:44:43 -07:00
Marcelo Fabri 0504d2c3f9
Migrate `function_parameter_count` to SwiftSyntax (#4394) 2022-10-17 00:44:17 -07:00
Marcelo Fabri 07be57027a
Add more rewritten rules to CHANGELOG 2022-10-16 23:55:42 -07:00
Marcelo Fabri 844b7c02dd
Rewrite `trailing_comma` rule with SwiftSyntax (#4385) 2022-10-16 23:36:11 -07:00
Marcelo Fabri b4ec2d360d
Rewrite `unused_control_flow_label` with SwiftSyntax (#4339) 2022-10-16 22:58:01 -07:00
Marcelo Fabri 89c227ecaa
Migrate `weak_delegate` rule to SwiftSyntax (#4389)
* Migrate `weak_delegate` rule to SwiftSyntax

* Remove unused declarations
2022-10-16 22:57:42 -07:00
Marcelo Fabri 6f4b75b0f6
Migrate `legacy_constructor` to SwiftSyntax (#4386) 2022-10-16 22:56:25 -07:00
Marcelo Fabri 4522005ab4
Migrate `implicitly_unwrapped_optional` to SwiftSyntax (#4278)
* Migrate `implicitly_unwrapped_optional` to SwiftSyntax

* Update Source/SwiftLintFramework/Rules/RuleConfigurations/ImplicitlyUnwrappedOptionalConfiguration.swift

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>

* Fix after rebase

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>
2022-10-16 22:55:32 -07:00
Marcelo Fabri 56a23448c3
Migrate `unused_enumerated` to SwiftSyntax (#4391) 2022-10-16 22:34:26 -07:00
Marcelo Fabri bb1de21059
Convert `enum_case_associated_values_count` to SwiftSyntax (#4393) 2022-10-16 22:34:12 -07:00
Marcelo Fabri 89c24cd52d
Rewrite `fatal_error_message` rule with SwiftSyntax (#4230) 2022-10-16 20:52:09 -04:00
Danny Mösch 0fbd03cf2b
Require visitors to exist (#4383) 2022-10-16 18:40:12 +02:00
Marcelo Fabri 8b96aa0585
Make UnavailableConditionRule conform to SwiftSyntaxRule (#4388) 2022-10-16 07:49:36 -07:00
Marcelo Fabri 7ff3094821
Support .focused() in keys for corrections (#4384) 2022-10-16 07:50:10 -04:00
Danny Mösch f8e5339c69
Introduce `ReasonedRuleViolation` type to associate a reason with a violation position (#4379) 2022-10-16 12:53:38 +02:00
Marcelo Fabri 9959f971f9
Add way to preprocess syntaxTree in SwiftSyntaxRule (#4381)
* Add way to preprocess syntaxTree in SwiftSyntaxRule

* Update Source/SwiftLintFramework/Protocols/SwiftSyntaxRule.swift

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>

* Update Source/SwiftLintFramework/Protocols/SwiftSyntaxRule.swift

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>

Co-authored-by: Danny Mösch <danny.moesch@icloud.com>
2022-10-16 01:40:02 -07:00
Danny Mösch dcf03f5987
Fix compilation errors (#4380) 2022-10-15 22:49:16 -04:00
Marcelo Fabri 860b027005
Migrate `private_over_fileprivate` to SwiftSyntax (#4376) 2022-10-15 14:49:16 -07:00
Marcelo Fabri 4aa9588454
Rewrite `function_default_parameter_at_end` with SwiftSyntax (#4320)
* Rewrite `function_default_parameter_at_end` with SwiftSyntax

* Fix oss-check issues

* Handle optional closures

* Handle other closure types

* Fix violation

* Handle @autoclosure
2022-10-15 14:48:59 -07:00
Marcelo Fabri 37bf2c9168
Rewrite `discouraged_none_name` with SwiftSyntax (#4377)
* Rewrite `discouraged_none_name` with SwiftSyntax

* Handle `none`
2022-10-15 14:48:38 -07:00
Danny Mösch bd8c9e5bcb
Provide syntax visitor base class allowing for convenient skipping of declaration nodes (#4310) 2022-10-15 19:03:49 +02:00
Marcelo Fabri 510cb39b11
Rewrite `quick_discouraged_pending_test` with SwiftSyntax (#4378) 2022-10-15 04:21:38 -07:00
Marcelo Fabri c9ef157c29
Rewrite `conditional_returns_on_newline` with SwiftSyntax (#4340)
* Rewrite `conditional_returns_on_newline` with SwiftSyntax

* Keep original behavior

* PR feedback
2022-10-15 02:25:56 -07:00
Marcelo Fabri e74e11533c
Rewrite `valid_ibinspectable` rule with SwiftSyntax (#4322)
* Rewrite `valid_ibinspectable` rule with SwiftSyntax

* Fix false positives
2022-10-14 20:32:03 -07:00
Danny Mösch a7ff8a8d76
Rename `if_let_shadowing` rule to `shorthand_optional_binding` (#4334) 2022-10-14 21:47:37 +00:00
JP Simard 7585aef910
Rewrite `reduce_boolean` with SwiftSyntax (#4375) 2022-10-14 07:03:30 -04:00
JP Simard f04e28cbf8
Rewrite `switch_case_alignment` with SwiftSyntax (#4373) 2022-10-14 10:06:54 +00:00
JP Simard 39bb05f7d1
Rewrite "body length" rules with SwiftSyntax (#4370)
- `closure_body_length`
- `function_body_length`
- `type_body_length`
2022-10-14 03:50:39 -04:00
JP Simard 7624059caa
Rewrite `unused_closure_parameter` with SwiftSyntax (#4371) 2022-10-14 03:35:51 -04:00
JP Simard fa6bf50a22
Rethink body line count calculation (#4369)
A long-standing limitation with SourceKit's "editor open" request is
that we weren't able to get certain tokens, such as braces, brackets and
parentheses.

This meant that this code block would be counted as two lines:

```swift
print(
  "hi"
)
```

because the trailing `)` would be treated as a whitespace line.

This meant that our "body length" family of rules that measure the
effective line count of declarations like functions, types or closures
would often significantly under-count the number of content lines in a
body.

Now with SwiftSyntax, we can get all tokens, including the ones
SourceKit was previously ignoring, so we can get much more accurate line
counts when ignoring whitespace and comments.

In addition, we weren't very thorough in how we measured body length.

As an exercise, how many lines long would you say the body of this
function is?

```swift
func hello() {
  print("hello")
}
```

Does the body span one line or three lines?

I propose that we consistently ignore the left and right brace lines
when calculating the body line count of these scopes so that we measure
body line counts like this:

```swift
// 1 line
{ print("foo") }
// 1 line
{
}
// 1 line
{
  print("foo")
}
// 2 lines
{
  let sum = 1 + 2
  print(sum)
}
```

Now with those changes in place, in order to keep the default
configuration thresholds to similar levels as before, we need to adjust
them slightly. Here's what I'm suggesting:

|Rule|Before|After|
|-|-|-|
|closure_body_length|20/100|30/100|
|function_body_length|40/100|50/100|
|type_body_length|200/350|250/350|

This is a pretty significant breaking change and I suspect we'll hear
from users who are surprised that some of their declarations now exceed
the rule limits, but I believe this new approach to calculating body
lines is more correct and intuitive compared to what we've had until
now.

OSSCheck is also going to report a bazillion changes with this, which is
expected given the scope of this change.
2022-10-14 03:16:26 -04:00
JP Simard 5c3d4c1dab
Rewrite `legacy_hashing` with SwiftSyntax (#4367) 2022-10-13 16:27:54 +00:00
JP Simard 02b5a034d2
Rewrite `is_disjoint` with SwiftSyntax (#4368) 2022-10-13 12:22:57 -04:00
JP Simard 48fde2321f
Rewrite `contains_over_first_not_nil` & `last_where` with SwiftSyntax (#4366)
Replace `MemberAccessExprSyntax.functionCallBase` with
`ExprSyntax.asFunctionCall` so we can use it in more places.

Move `TokenKind.isEqualityComparison` into `SwiftSyntax+SwiftLint.swift`
2022-10-13 15:59:30 +00:00
JP Simard 8d29b21ecb
Rewrite `sorted_first_last` & `first_where` with SwiftSyntax (#4365)
Add a `functionCallBase` helper.
2022-10-13 15:30:22 +00:00
JP Simard 3bae16bee5
Rewrite `contains_over_filter_is_empty` with SwiftSyntax (#4364) 2022-10-13 10:45:40 -04:00
JP Simard 33f6ee1f36
Update SwiftSyntax (#4363)
Noteworthy:

* https://github.com/apple/swift-syntax/pull/912
* https://github.com/apple/swift-syntax/pull/932
2022-10-13 10:28:00 -04:00
JP Simard de0b91cd5a
Print violations in realtime if progress with an output file (#4362) 2022-10-13 06:23:24 +00:00
JP Simard 4e3c750086
Rewrite `attributes` with SwiftSyntax (#4359)
Not 100% the same as it previously was, but I think all the changes
reported by OSSCheck are improvements or neutral.
2022-10-13 00:40:24 -04:00
JP Simard 2f300502c0
Fix changelog formatting (#4361) 2022-10-13 00:36:25 -04:00
Danny Mösch 9db88947b9
Fix bugs when auto-correcting content given via stdin (#4212)
Fixes #4211.
Fixes #4234.
Fixes #4347.
2022-10-13 00:34:48 -04:00
Marcelo Fabri 99436906cd
Rewrite `self_in_property_initialization` with SwiftSyntax (#4343) 2022-10-12 20:58:57 -07:00
JP Simard 7653b2d635
Update link to video presentation in readme (#4360)
This link has had a broken video resource for years.

The video was just re-published.
2022-10-12 22:49:45 -04:00
Danny Mösch b4e01ba1b4
Disregard whitespace differences in `identical_operands` rule (#4331) 2022-10-12 23:38:00 +02:00
JP Simard b9653e3e1b
Replace `swift run` with `swift build --product` (#4357) 2022-10-12 15:51:10 -04:00
JP Simard 9aaeff67d0
Add `SyntaxProtocol.isContainedIn(regions:locationConverter:)` helper (#4356) 2022-10-12 19:50:32 +00:00
JP Simard 1a7f03bbc5
Rewrite `redundant_discardable_let` with SwiftSyntax (#4355) 2022-10-12 15:27:03 -04:00
JP Simard b849234572
Remove `Configuration.FileGraph.Vertix.configurationString` property (#4354)
It's unused and for large configuration files this uses unnecessary
memory.
2022-10-12 17:16:27 +00:00
JP Simard 0282bf1923
Remove optionality in getting syntax tree and source location converter (#4353)
Parsing does not throw errors.
See https://github.com/apple/swift-syntax/pull/912.
2022-10-12 12:50:17 -04:00
JP Simard 4b04e528af
Revert "Run TSan job on Azure Pipelines with Bazel (#4130)" (#4350)
This reverts commit 848ec452c3.

This appears to be slower most of the time.
2022-10-12 14:26:45 +00:00
JP Simard 602070164b
Reduce memory usage (#4349)
When linting SwiftLint, this brings memory usage down by around 20%,
going from 372MB to 297MB.

This is achieved by removing the unused `RebuildQueue` and by clearing
cached data associated with files after processing them.

Depends on https://github.com/jpsim/SourceKitten/pull/749.
2022-10-12 09:39:36 -04:00
JP Simard 87e9757af2
Update SwiftSyntax (#4346)
I think there might have been changes impacting the `static_operator`
rule, so I want to look more into those here.

Remove workaround for https://github.com/apple/swift-syntax/issues/888
2022-10-11 21:12:29 -04:00
Marcelo Fabri 81ee297908
Rewrite `unused_setter_value` rule with SwiftSyntax (#4342) 2022-10-11 13:08:40 -07:00
JP Simard 016c7b6abd
[Bazel] Fix CryptoSwift compiler warnings (#4345)
Pulls in https://github.com/krzyzanowskim/CryptoSwift/pull/989
2022-10-11 14:22:22 -04:00
Marcelo Fabri 6998d8af23
Migrate `shorthand_operator` rule to SwiftSyntax (#4336)
* Migrate `shorthand_operator` rule to SwiftSyntax

* Remove unneeded imports
2022-10-10 21:28:55 -07:00
Marcelo Fabri 4da2ff1118
Rewrite `operator_whitespace` rule with SwiftSyntax (#4335) 2022-10-10 14:12:03 -07:00
Marcelo Fabri 05d98b84cf
Rewrite `quick_discouraged_focused_test` with SwiftSyntax (#4338) 2022-10-10 14:11:37 -07:00
Marcelo Fabri ec05864880
Rewrite `nslocalizedstring_require_bundle` with SwiftSyntax (#4337) 2022-10-10 14:11:28 -07:00
Marcelo Fabri b938a4e07d
Convert `number_separator` rule to SwiftSyntax (#4333) 2022-10-09 21:03:57 -07:00
Marcelo Fabri ca7510a394
Migrate `static_operator` rule to SwiftSyntax (#4271)
* Migrate `static_operator` rule to SwiftSyntax

* PR feedback
2022-10-08 01:49:15 -07:00
Marcelo Fabri 04bd091810
Migrate `legacy_multiple` rule to SwiftSyntax (#4317) 2022-10-07 23:49:14 -07:00
Danny Mösch 82e916db15
Extend SyntaxProtocol to avoid wrapping (#4332) 2022-10-07 19:41:08 -04:00
Danny Mösch 957208c44b
Make `nsobject_prefer_isequal` rule work for nested classes (#4329) 2022-10-08 00:21:16 +02:00
JP Simard fe6a930bd4
Use os_unfair_lock instead of NSLock on Darwin (#4330) 2022-10-07 20:42:22 +00:00
Marcelo Fabri 8bcf8713e9
Convert `discouraged_assert` rule to SwiftSyntax (#4316) 2022-10-07 13:40:23 -07:00
Marcelo Fabri d913c8e63b
Rewrite `legacy_random` rule with SwiftSyntax (#4318) 2022-10-07 13:35:13 -07:00
Marcelo Fabri b01e1504a1
Rewrite `prohibited_interface_builder` with SwiftSyntax (#4321) 2022-10-07 13:33:49 -07:00
JP Simard 3ac518ce69
Only build `swiftlint` target in Makefile (#4327)
Otherwise this tries to build the SwiftLintTestHelpers module in release
mode, which fails.

Also, to build the actual executable CLI, we can't just
`swift build --target=swiftlint` because that doesn't produce a CLI,
just the object file for the binary. So we have to `swift run`.
2022-10-07 16:50:56 +00:00
JP Simard 0c06b7f65f
Fix jpsim GitHub URL in changelog 2022-10-07 12:14:21 -04:00
JP Simard 1a76a882ca
Ignore incorrect keypath parser diagnostics (#4325)
Works around https://github.com/apple/swift-syntax/issues/888
2022-10-07 10:33:13 -04:00
JP Simard f34169575c
Update rules_apple to 1.1.2 (#4324)
https://github.com/bazelbuild/rules_apple/releases/tag/1.1.2
2022-10-07 10:22:23 -04:00
JP Simard 9c53c94736
Update rules_xcodeproj to 0.9 (#4323)
https://github.com/buildbuddy-io/rules_xcodeproj/releases/tag/0.9.0
2022-10-07 10:14:46 -04:00
Marcelo Fabri 515587fb9f
Convert `strict_fileprivate` rule to SwiftSyntax (#4319) 2022-10-07 02:11:32 -07:00
Marcelo Fabri f174a55c94
Migrate `explicit_enum_raw_value` rule to SwiftSyntax (#4281)
* Migrate `explicit_enum_raw_value` rule to SwiftSyntax

* Handle nested enums
2022-10-07 00:56:15 -07:00
Marcelo Fabri c0a4dd1be7
Rewrite IdenticalOperandsRule with SwiftSyntax (#3894)
* Rewrite IdenticalOperandsRule with SwiftSyntax

* Use folding algorithm from swift-format

* Add workaround for debug builds

* Update implementation after rebasing

* Use SwiftOperators

* Remove focused
2022-10-07 00:55:41 -07:00
JP Simard 1533e72fd4
Specify `@SwiftLint` workspace in opt_wrapper.bzl load (#4314) 2022-10-06 16:59:24 -04:00
JP Simard 549c71a27c
Introduce LegacyFunctionRuleHelper (#4312) 2022-10-06 16:48:51 -04:00
Keith Smiley 367389a489
[bazel] For SwiftSyntax to compile with optimizations (#4311)
This avoids potential stack overflows and slowness when just working on
SwiftLint itself with bazel. These compiles take a lot longer but as
long as you're not updating swift-syntax that should be a 1 time penalty
2022-10-06 15:54:41 -04:00
JP Simard ef248f6529
Remove Signal from OSSCheck (#4313)
It's misbehaving for some reason. It'd be good if anyone wanted to look
into that.
2022-10-06 15:40:18 -04:00
JP Simard a6c90dd942
Rewrite `legacy_cggeometry_functions` with SwiftSyntax (#4309) 2022-10-06 15:16:49 -04:00
Danny Mösch 2a95f1bde9
Add tests for `fileprivate` to `private_outlet` rule (#4308) 2022-10-06 19:13:25 +02:00
JP Simard 5ba9d60f8d
Rewrite `legacy_nsgeometry_functions` with SwiftSyntax (#4307) 2022-10-06 12:46:27 -04:00
Danny Mösch 8345545865
Make `private_unit_test` rule correctable (#4293) 2022-10-06 18:03:03 +02:00
JP Simard d6fd754679
Rewrite `contains_over_range_nil_comparison` with SwiftSyntax (#4225)
Use SwiftOperators.
2022-10-06 01:18:31 -04:00
JP Simard a8d3813cc7
Fix Dockerfile to account for new test modules (#4306) 2022-10-06 00:36:04 -04:00
JP Simard 5ae1d98596
Create new GeneratedTests and IntegrationTests test targets (#4304)
* Move generated rule tests to a dedicated GeneratedTests target
* Move integration tests to a dedicated IntegrationTests target

This will improve bazel cacheability by having fewer tests depend on all
lint inputs, which are only needed by the integration tests.
2022-10-06 00:10:04 -04:00
JP Simard d3fd234883
Fix Dockerfile to account for SwiftLintTestHelpers (#4305) 2022-10-05 23:53:14 -04:00
JP Simard 503f8c506b
Move ExtraRulesTests to its own target & directory (#4300)
Also create a SwiftLintTestHelpers test target.
2022-10-05 23:36:46 -04:00
JP Simard 8470b65ae9
Simplify analyze bazel test (#4303)
By removing SourceAndTestFiles since those files should all be covered
by LintInputs.
2022-10-05 23:25:54 -04:00
JP Simard 4552facb7e
[LegacyConstantRule] Remove unused patterns (#4302)
We now just need these patterns for the mapping of legacy to new.
2022-10-05 22:42:09 -04:00
JP Simard c29e1673dd
Rewrite `legacy_constant` with SwiftSyntax (#4299) 2022-10-05 21:55:48 -04:00
JP Simard cfabd8ec99
Organize `Tests/BUILD` with comments (#4301) 2022-10-05 21:40:12 -04:00
JP Simard 91d4a42687
Loosen correction location assertions (#4297)
With SwiftSyntax rewriters, the visitors get called with the new nodes
after previous mutations have been applied, so it's not straightforward
to translate those back into the original source positions.

So only check the first locations.
2022-10-05 20:51:46 -04:00
JP Simard 7af26f0f77
[CI] Increase TSan test timeout to 1,000 seconds (~16 minutes) (#4298)
We're starting to hit this timeout.
2022-10-05 20:45:07 -04:00
Marcelo Fabri c087e79437
[oss-check] Add DuckDuckGo and Signal (#4275) 2022-10-05 19:00:37 -04:00
JP Simard 54ec0c9b28
Rewrite `trailing_semicolon` with SwiftSyntax (#4296) 2022-10-05 18:46:14 -04:00
JP Simard 3834e52ac4
Rewrite `empty_parameters` with SwiftSyntax (#4295) 2022-10-05 18:26:57 -04:00
JP Simard 5d9a72b2a5
Make `allow_private_set` allow `fileprivate(set)` too (#4294)
As was previously the case.
2022-10-05 17:32:28 -04:00
Danny Mösch af222a5754
Ignore static methods in `private_unit_test` rule (#4292)
Static methods cannot be tests.
2022-10-05 21:00:06 +00:00
JP Simard 54ccc39e72
Update SwiftSyntax (#4290)
Just staying up to date here.

There's no need to frequently update, but given that we just updated to
trunk, I wanted to make sure we can update without issues.
2022-10-05 16:28:56 -04:00
JP Simard 8b578ebf36
Update changelog entry for rules rewritten using SwiftSyntax (#4291) 2022-10-05 16:11:04 -04:00
JP Simard 2eb764d0c9
Update Dockerfile for plugin support (#4289) 2022-10-05 15:34:55 -04:00
JP Simard 13c45a5c15
Add resolved issues to plugin changelog entry (#4288) 2022-10-05 15:31:11 -04:00
Johannes Ebeling 3fd1573c57
Make SwiftLint available as a build tool plugin (#4176)
*  (spm) add build tool plugin definition for swiftlint
* 📝 (changelog) add changelog item for the plugin
* 🐛 (cache) define cache path explicitly to avoid issues during build without issues
* 🎨 minor code cleanup
* 📝 document usage with Xcode as well as Swift packages
2022-10-05 15:28:19 -04:00
JP Simard 6d75645f67
[CI] Disable xcodebuild tests (#4287)
This is failing very often now due to FB11648454.

Given that we run these tests on macOS via SwiftPM and Bazel as well,
I'm satisfied with the amount of coverage we have on our tests without
this.
2022-10-05 15:26:14 -04:00
Danny Mösch 8eec5e6997
Rewrite `private_unit_test` with SwiftSyntax (#4285) 2022-10-05 21:15:25 +02:00
JP Simard 44382ea397
Avoid making SourceKit requests to get parser diagnostics (#4286)
By using SwiftSyntax instead, we avoid the overhead of a SourceKit
request, which has a significant impact if none of the rules being
corrected use SourceKit.
2022-10-05 15:38:11 +00:00
JP Simard 41b8834a13
Rewrite `private_outlet` with SwiftSyntax (#4228) 2022-10-05 11:29:01 -04:00
Marcelo Fabri a40cc70478
Migrate `no_extension_access_modifier` to SwiftSyntax (#4270)
* Migrate `no_extension_access_modifier` to SwiftSyntax

* PR feedback
2022-10-05 00:56:42 -04:00
Marcelo Fabri 1cbf3caae1
Migrate `anonymous_argument_in_multiline_closure` to SwiftSyntax (#4279) 2022-10-04 20:57:22 -07:00
JP Simard 0f9705901b
Add column for SourceKit usage to `rules` command (#4284) 2022-10-04 18:52:03 +00:00
Vasiliy Kattouf 129a55fb74
Add ability to filter rules for `generate-docs` subcommand (#4195)
Added rules filtering options, like in the `rules` subcommand.

<img width="880" alt="generate-docs--help" src="https://user-images.githubusercontent.com/7829589/189372666-2f5aba62-57a1-49dc-9155-c60f9e085984.png">

To achieve reuse with `rules` subcommand:
- extracted filtering logic into `RulesFilter` (and write tests)
- extracted filtering command line parameters into `RulesFilterOptions: ParsableArguments`
2022-10-04 18:45:52 +00:00
Marcelo Fabri c70510c3ea
Migrate `strong_iboutlet` to SwiftSyntax (#4277) 2022-10-04 10:03:33 -07:00
JP Simard 6bfecb8fcc
Rewrite `nsobject_prefer_isequal` with SwiftSyntax (#4283) 2022-10-04 16:34:24 +00:00
JP Simard db20dbb4a3
Replace mutating for-in with `reduce(into:)` (#4282) 2022-10-04 11:47:27 -04:00
JP Simard 46649c8662
Rewrite `explicit_init` with SwiftSyntax (#4223) 2022-10-04 11:20:00 -04:00
JP Simard 1662a38922
Rewrite `ibinspectable_in_extension` with SwiftSyntax (#4227) 2022-10-04 09:28:46 -04:00
JP Simard 18ab68859c
Rewrite `contains_over_filter_count` with SwiftSyntax (#4226) 2022-10-04 09:14:27 -04:00
Danny Mösch ab3f070222
Introduce `SeverityBasedRuleConfiguration` to avoid custom `makeViolation`s (#4274) 2022-10-04 07:39:07 +02:00
Marcelo Fabri e3f1b56129
Convert `duplicate_enum_cases` rule to SwiftSyntax (#4263) 2022-10-03 13:20:43 -07:00
Marcelo Fabri 26ca731cf0
Migrate `deployment_target` rule to SwiftSyntax (#4268) 2022-10-03 10:18:56 -07:00
Marcelo Fabri 8926596da0
Migrate `closure_parameter_position` to SwiftSyntax (#4264) 2022-10-03 10:18:43 -07:00
Marcelo Fabri 647819eedc
Migrate `switch_case_on_newline` to SwiftSyntax (#4269) 2022-10-03 09:28:29 -07:00
Marcelo Fabri f37ea8acc4
Migrate `no_fallthrough_only` rule to SwiftSyntax (#4266) 2022-10-03 09:28:14 -07:00
Marcelo Fabri ac91f7bfa0
Migrate `private_action` rule to SwiftSyntax (#4267) 2022-10-03 09:27:29 -07:00
Marcelo Fabri 462f741065
Rewrite `redundant_string_enum_value` with SwiftSyntax (#4243) 2022-10-02 17:29:55 -07:00
Marcelo Fabri 268415893d
Rewrite protocol_property_accessors_order with SwiftSyntax (#4237)
* Rewrite protocol_property_accessors_order with SwiftSyntax

* PR feedback
2022-10-02 17:29:04 -07:00
JP Simard 2388e49190
Use SwiftSyntax's new SwiftParser (#4216) 2022-10-01 15:05:36 -04:00
JP Simard b2caef7202
Generate Swift Syntax Dashboard in documentation (#4260)
This is a useful overview of where the migration effort stands and what
rules can be migrated next.
2022-10-01 11:29:10 -04:00
Danny Mösch 2efd538d37
Explain where to get syntax kinds for custom rules from (#4246) 2022-09-30 20:23:05 +02:00
Benjamin Kramer 0fe3404494
Add configuration `only_enforce_before_trivial_lines` to `vertical_whitespace_closing_braces` rule (#3941)
Vertical whitespace may be important for readability when the line
with the closing brace is not a trivial one (e.g. `} else if ... {`).
The configuration allows not enforcing the rule in such cases.
2022-09-29 20:26:51 +02:00
Danny Mösch da75cdca40
Remove instructions for Atom editor (#4255)
Atom editor will be sunset at the end of the year.
2022-09-29 06:49:35 +02:00
JP Simard 86f6023d13
Update CryptoSwift to 1.6.0 release (#4258) 2022-09-28 23:37:54 -04:00
JP Simard afb46dc76d
Remove anyobject_protocol from configuration (#4257)
It's been deprecated and the compiler catches these now.
2022-09-28 23:23:06 -04:00
Marcelo Fabri a40488feaf
Rewrite `inert_defer` rule with SwiftSyntax (#4242) 2022-09-28 19:57:00 -07:00
JP Simard d2c638b5e4
[bazel] Enable WMO in `release` configuration (#4252)
Enabling WMO can make SwiftLint up to 90% faster in my testing, and
matches what SwiftPM does when building with `swift buid -c release`.
2022-09-27 21:56:17 -04:00
JP Simard 86a176d4c6
[bazel] Fix release config (#4253) 2022-09-27 21:39:29 -04:00
JP Simard 73def99b49
[bazel] Define a `release` configuration (#4251)
This will be used to put release configurations, like WMO.
2022-09-27 21:34:27 -04:00
JP Simard fd7afedfcf
Update SwiftSyntax & internal parser for Swift 5.7 (#4203)
Require Swift 5.7 to compile
2022-09-26 14:11:39 +00:00
Danny Mösch c6eec8072c
Move method (#4238) 2022-09-25 12:31:24 -04:00
Marcelo Fabri a4c46f624d
Rewrite `no_space_in_method_call` with SwiftSyntax (#4236) 2022-09-25 02:55:00 -07:00
Marcelo Fabri 734eceada0
Rewrite empty_parentheses_with_trailing_closure with SwiftSyntax (#4235) 2022-09-25 02:54:29 -07:00
JP Simard 4fb5ebe19b
Docker: Update to Swift 5.7 & Ubuntu Jammy (#4241)
And apply workaround for https://github.com/apple/swift/issues/59961 to
all commands.
2022-09-24 15:09:41 -04:00
JP Simard bb309de9df
Pin Swift Docker image to 5.6.3-focal (#4240) 2022-09-24 13:56:41 -04:00
Danny Mösch 7be6f866fe
Add new `if_let_shadowing` rule (#4206) 2022-09-22 19:27:45 +02:00
Martin Redington 0db26db975
Add `test_parent_classes` option to `test_case_accessibility` rule (#4214) 2022-09-21 18:29:32 +02:00
Marcelo Fabri f8a4276aba
Rewrite `generic_type_name` rule with SwiftSyntax (#4229) 2022-09-21 08:29:52 -07:00
Danny Mösch 95ffaed655
Filter out compiler argument not understood by SourceKit in Xcode 14 (#4210) 2022-09-20 23:24:40 +02:00
JP Simard 7b1de2f9cd
Rewrite `dynamic_inline` with SwiftSyntax (#4218) 2022-09-20 20:07:24 +00:00
JP Simard 49665a9ec7
Rewrite `fallthrough` with SwiftSyntax (#4224) 2022-09-20 15:56:57 -04:00
Danny Mösch 0ea26edd4f
Skip autocorrecting usage of `NSIntersectionRect` (#4213) 2022-09-20 07:09:05 +02:00
Danny Mösch 2efa085dd4
Make `willMove` a lifecycle method in `type_contents_order` rule (#4201) 2022-09-19 23:27:15 +02:00
dahlborn f3d367f667
Add `LibraryContentProvider` to `file_types_order` rule (#4209) 2022-09-19 22:15:41 +02:00
JP Simard 345a904055
Rewrite `empty_enum_arguments` with SwiftSyntax (#4221) 2022-09-19 20:12:21 +00:00
JP Simard 0808f2508b
Fix typo in example (#4222) 2022-09-19 15:51:21 -04:00
JP Simard 8d500d1a63
Rewrite `empty_collection_literal` with SwiftSyntax (#4220) 2022-09-19 12:39:35 +02:00
JP Simard 992cc9afe3
Rewrite `discouraged_object_literal` with SwiftSyntax (#4219) 2022-09-19 10:24:22 +00:00
Danny Mösch 49ace1729f
Add Swift 5.6.3 to version test (#4207) 2022-09-14 23:20:51 +02:00
Danny Mösch 38e71f7a10
Enhance docs for `type_name` rule (#4197) 2022-09-10 10:54:13 -04:00
tillhainbach a9ec894caf
Add exceptions to `weak_delegate` rule (#3599) 2022-09-10 10:07:43 -04:00
Chris Brakebill 9db303e3a3
Respect 'validates_start_with_lowercase' for function names in `identifier_name` rule (#4188) 2022-09-09 23:36:55 +02:00
JP Simard 4b1bb38fc3
Include the configured `bind_identifier` in `self_binding` violation (#4196)
As suggested in https://github.com/realm/SwiftLint/pull/4146#issuecomment-1241793057
2022-09-09 16:08:26 +00:00
Marcelo Fabri 623bc8ea73
Add multiple_closures_with_trailing_closure to changelog 2022-09-08 19:34:37 +02:00
Marcelo Fabri baf5647e2b
Rewrite `multiple_closures_with_trailing_closure` with SwiftSyntax (#4193) 2022-09-08 19:34:06 +02:00
JP Simard ea574bd640
Update changelog to reflect rules rewritten with SwiftSyntax (#4191) 2022-09-08 05:50:58 +00:00
JP Simard 837f46479f
Rewrite `toggle_bool` with SwiftSyntax (#4190) 2022-09-08 05:27:44 +00:00
JP Simard c6f3d0a6af
Rewrite `xctfail_message` with SwiftSyntax (#4189) 2022-09-08 00:03:44 -04:00
JP Simard 8c5d3cbfc8
Use Xcode 14 RC1 on CI (#4186)
https://twitter.com/XcodeReleases/status/1567607977900929024
2022-09-07 22:58:15 -04:00
Danny Mösch 311a724c3c
Get rid of optional return value (#4187) 2022-09-07 18:18:34 -04:00
Danny Mösch 6b393b1b53
Re-delegate to ArrayInitRule (#4185) 2022-09-07 21:14:45 +00:00
JP Simard 4234664671
Rewrite `anyobject_protocol` with SwiftSyntax & deprecate (#4170) 2022-09-07 19:40:34 +00:00
JP Simard 8cd025112d
Rewrite `block_based_kvo` with SwiftSyntax (#4171) 2022-09-07 15:16:22 -04:00
JP Simard efbe4d9f0f
Rewrite `discouraged_optional_boolean` with SwiftSyntax (#4179) 2022-09-07 15:15:38 -04:00
JP Simard 4a239a695e
Rewrite `array_init` rule with SwiftSyntax (#4175) 2022-09-07 15:15:10 -04:00
JP Simard bc7792ecd6
Rewrite `empty_string` with SwiftSyntax (#4177) 2022-09-07 13:59:15 -04:00
JP Simard 958cc20a9f
Rewrite `redundant_nil_coalescing` with SwiftSyntax (#4182) 2022-09-07 13:29:06 -04:00
JP Simard 117ef7f0e2
Rewrite `flatmap_over_map_reduce` with SwiftSyntax (#4178) 2022-09-07 13:23:51 -04:00
JP Simard 0d03221281
Rewrite `class_delegate_protocol` with SwiftSyntax (#4172) 2022-09-07 12:20:47 -04:00
JP Simard 80c8e5d37a
Rewrite `closing_brace` with SwiftSyntax (#4173) 2022-09-07 12:20:26 -04:00
JP Simard 9060d3f8e8
Rewrite `force_try` with SwiftSyntax (#4181) 2022-09-07 11:18:01 -04:00
JP Simard eb30be3826
Add `SourceKitFreeRule` conformance to `FileNameNoSpaceRule` (#4180) 2022-09-07 10:36:57 -04:00
JP Simard 2a8d536857
Remove delegation of TypesafeArrayInitRule to ArrayInitRule (#4174)
In preparation for rewriting `ArrayInitRule` with SwiftSyntax.
2022-09-07 01:34:58 -04:00
Ryan Cole c34955ca64
Add `accessibility_trait_for_button` rule (#3989) 2022-09-06 21:44:09 +00:00
Danny Mösch e459c4ae9b
Exclude `weak_delegate` rule from autocorrection (#4153) 2022-09-06 22:56:51 +02:00
Marcelo Fabri 75a6d14788
Update CHANGELOG.md 2022-09-06 09:25:55 -07:00
Marcelo Fabri e2900c4a7e
Rewrite `untyped_error_in_catch` with SwiftSyntax (#4163) 2022-09-06 09:25:06 -07:00
Marcelo Fabri dd94d4ec53
Rewrite `computed_accessors_order` with SwiftSyntax (#4165) 2022-09-06 09:24:54 -07:00
Marcelo Fabri 1e7e0d6301
Remove references to ImplicitlyUnwrappedOptional type (#4166) 2022-09-06 09:01:22 -07:00
JP Simard 4b8a68072b
Call `super.visit` in `SyntaxRewriter` overrides (#4167)
As discussed in https://github.com/realm/SwiftLint/pull/4159#discussion_r962841482
2022-09-06 13:52:40 +00:00
JP Simard 26465f21ce
Add methods from SE-0348 to `UnusedDeclarationRule` (#4168)
https://github.com/apple/swift-evolution/blob/main/proposals/0348-buildpartialblock.md
2022-09-06 09:45:34 -04:00
Marcelo Fabri b29c07e07e
Update CHANGELOG.md 2022-09-05 20:33:01 -07:00
Marcelo Fabri 622d48bd0a
Convert `unneeded_break_in_switch` to SwiftSyntax (#4164) 2022-09-05 20:32:29 -07:00
Marcelo Fabri b9e6a9aed8
Rewrite unneeded_parentheses_in_closure_argument with SwiftSyntax (#4159) 2022-09-05 06:12:33 -04:00
Marcelo Fabri 6c0413077c
Rewrite unowned_variable_capture with SwiftSyntax (#4161) 2022-09-05 05:38:29 -04:00
Marcelo Fabri 772e5f5806
Delegate Location init for AbsolutePosition to existing one (#4162) 2022-09-05 01:47:25 -04:00
Marcelo Fabri c7c4e0a1f3
Rewrite `implicit_getter` rule with SwiftSyntax (#4160)
* Rewrite `implicit_getter` rule with SwiftSyntax

* Handle different reason messages

* Add changelog + implicit return
2022-09-05 01:28:25 -04:00
Marcelo Fabri ca2d3a874f
Rewrite `large_tuple` rule with SwiftSyntax (#4156)
* Rewrite `large_tuple` rule with SwiftSyntax

* Add changelog, remove import

* Update Source/SwiftLintFramework/Rules/Metrics/LargeTupleRule.swift

Co-authored-by: JP Simard <jp@jpsim.com>

Co-authored-by: JP Simard <jp@jpsim.com>
2022-09-05 01:10:59 -04:00
Marcelo Fabri 79347a1728
Rewrite ForceUnwrappingRule using SwiftSyntax (#4155) 2022-09-04 02:08:26 -07:00
Marcelo Fabri c1650e6918
Add `excludes_trivial_init` for `missing_docs (#4152)
Fixes #4107
2022-09-03 22:11:57 -07:00
JP Simard c26c40d31f
Set `.bazelversion`
To avoid this issue: https://github.com/bazelbuild/bazelisk/issues/220
2022-09-02 10:46:17 -04:00
JP Simard 93158a609e
Update rules_xcodeproj to 0.7.1 2022-09-02 10:39:59 -04:00
JP Simard a3b70a0ffb
Update rules_apple/rules_swift to 1.1.1 (#4150)
https://github.com/bazelbuild/rules_apple/releases/tag/1.1.1
https://github.com/bazelbuild/rules_swift/releases/tag/1.1.1
2022-09-02 14:34:58 +00:00
JP Simard e381a7d684
Update bazel version in readme 2022-09-02 10:10:09 -04:00
JP Simard abad253310
Add empty changelog section 2022-09-01 12:43:37 -04:00
JP Simard 57dc1c9532
release 0.49.1 2022-09-01 12:32:19 -04:00
JP Simard d72877a34f
Move changelog entry 2022-09-01 12:29:13 -04:00
JP Simard ea41b9fd09
Rename default branch from `master` to `main` (#4116) 2022-09-01 07:09:46 -04:00
JP Simard e0f23fa8e9
Add `--progress` flag to lint and analyze commands (#4147)
Inspired by https://github.com/jkandzi/Progress.swift

```
4948 of 29100 [=====                         ] ETA: 44s (540 files/s)
```

Demo:

[![asciicast](https://asciinema.org/a/517985.svg)](https://asciinema.org/a/517985)
2022-08-31 18:26:17 -04:00
JP Simard 4bc6588f38
Add `--output` option to lint and analyze commands (#4148)
To write to a file instead of to stdout.

Addresses https://github.com/realm/SwiftLint/issues/4048
2022-08-31 17:34:37 -04:00
JP Simard d14cf598fd
Add SwiftSyntaxBuilder to SwiftLintFramework Bazel deps (#4149)
Even though we don't use this ourselves yet, it's useful to expose this
so that users with custom rules can access it.
2022-08-31 11:31:37 -04:00
JP Simard 9979c4fd27
Add new `self_binding` opt-in rule (#4146)
To enforce that `self` identifiers are consistently re-bound to a common
identifier name.

Configure `bind_identifier` to the name you want to use.

Defaults to `self`.

Addresses https://github.com/realm/SwiftLint/issues/2495
2022-08-30 16:37:19 -04:00
JP Simard f032c82b59
Fix link in comments (#4145) 2022-08-29 14:29:20 -04:00
JP Simard c8a66e7070
Add changelog entry for https://github.com/realm/SwiftLint/pull/4143 (#4144) 2022-08-29 12:59:23 -04:00
Martin Hosna f32bc1f337
RedundantDiscardableLetRule should not produce warning when used with async (#4143)
handle situation with async let _ = ....
let can not be removed from "async let _ = ..." because removing it produces invalid swift syntax

Co-authored-by: Martin Hosna <mhosna@samepage.io>
2022-08-29 12:56:35 -04:00
JP Simard fc3b143973
Rename `script` directory to `tools` (#4141)
And move the Danger bazel config there
2022-08-29 01:16:20 -04:00
JP Simard 4119c27857
Fix typo 2022-08-29 00:33:51 -04:00
JP Simard a9e5a644bb
Remove rbenv/ruby setup from CONTRIBUTING.md (#4140)
It's no longer needed as of https://github.com/realm/SwiftLint/pull/4138
2022-08-28 23:54:47 -04:00
JP Simard 501142d3e7
Update gems (#4139) 2022-08-29 03:29:37 +00:00
JP Simard ea6ca3e8dd
[CI] Use `ci.bazelrc` in Azure Pipelines 2022-08-28 23:00:17 -04:00
JP Simard 0fd9a112b4
[CI] Run Danger with Bazel (#4138)
It'd be nice to avoid needing to maintain rbenv on CI
2022-08-28 22:58:39 -04:00
JP Simard eb14f5283c
Speed up Analyze CI job (#4136)
By merging `write_swiftpm_yaml` into analyze test
2022-08-28 22:35:42 -04:00
JP Simard cd9bd5f9fd
Make duplicate_imports rule documentation deterministic (#4137) 2022-08-28 18:27:12 -04:00
Danny Mösch 9875ab904b
Print autocorrected STDIN input to STDOUT (#4132) 2022-08-28 23:17:20 +02:00
JP Simard 7412205b6c
[CI] Use Xcode 13.4.1 on Azure Pipelines (#4134) 2022-08-28 14:42:57 -04:00
JP Simard 8dbc0e4cbf
Update Azure Pipelines to use 'ubuntu-latest' for Linux jobs 2022-08-28 14:20:11 -04:00
JP Simard ccac242f3a
Update CI setup instructions again 2022-08-28 14:15:28 -04:00
JP Simard dcb5bf438d
Support `user.bazelrc`
So devs can put extra bazel flags in there
2022-08-28 14:09:22 -04:00
JP Simard fdcdcf76d1
Use bazel remote cache on Buildkite (#4133) 2022-08-28 14:07:10 -04:00
JP Simard 05461c8525
Update `CONTRIBUTING.md` with new CI steps for Bazel remote cache 2022-08-28 12:37:02 -04:00
JP Simard 848ec452c3
Run TSan job on Azure Pipelines with Bazel (#4130) 2022-08-28 10:51:58 -04:00
Danny Mösch bf9bf83143
Migrate EmptyXCTestMethodRule to SwiftSyntax fixing some false-positives (#4129) 2022-08-27 03:02:05 -04:00
JP Simard d9ec625601
Add empty changelog section 2022-08-26 11:08:56 -04:00
JP Simard ef36201e6b
release 0.49.0 2022-08-26 10:48:09 -04:00
JP Simard 5a30991fa4
Add SwiftSyntax rule helpers (#4126)
This cuts down on the boilerplate involved in writing SwiftSyntax-based
rules. May not be significant right now since most rules are still built
with SourceKit, but as we migrate more rules moving forward, this should
make it easier for rule authors to write rules that behave performantly
and correctly.
2022-08-26 14:46:32 +00:00
JP Simard aebded15ba
Fix changelog formatting 2022-08-26 10:16:16 -04:00
Ben Davis 7dfadc205a
Add new configuration to `operator_usage_whitespace` rule to specify no-space operators (#4112) 2022-08-25 17:59:27 +02:00
JP Simard 9cfd3dc978
Update CI to use Xcode 14 beta 6 (#4125)
Only used for the TSan CI job right now, which was previously using
Xcode 14 beta 5.

https://twitter.com/XcodeReleases/status/1562130365191315457
2022-08-23 15:31:25 -04:00
JP Simard c73f68683a
Update rules_apple to 1.1.0 (#4124)
https://github.com/bazelbuild/rules_apple/releases/tag/1.1.0
2022-08-23 15:17:38 -04:00
JP Simard cf764e7b90
Fix hang after linting or analyzing on Linux (#4123)
Fixes https://github.com/realm/SwiftLint/issues/4117
2022-08-23 13:45:10 -04:00
JP Simard 83bf37e642
Add `SwiftLintFile.isTestFile` (#4122) 2022-08-23 12:04:30 -04:00
JP Simard 7259c02ee5
Teach `--compile-commands` to parse SwiftPM yaml files (#4119)
And move analyze CI job to Bazel

Which will benefit from caching previous results,
so if we make a PR that doesn't impact the analysis
results (i.e. changelog or docs) we might be able to
skip running this at all.
2022-08-23 11:01:13 -04:00
JP Simard 68983d7da2
Enable Bazel disk cache (#4120)
Set to `~/.bazel_cache`. https://bazel.build/remote/caching#disk-cache
2022-08-23 07:58:31 -04:00
JP Simard 2960ae6a7a
Test with thread sanitizer in Buildkite using Bazel (#4115)
This will allow for the test to be skipped if it's already cached,
speeding up CI runs.
2022-08-22 19:21:54 -04:00
JP Simard 69f3a3b4b7
Refactor bazel release rule
Moving the signature file generation from the Makefile to the bazel rule
2022-08-22 12:26:22 -04:00
Danny Mösch f3e2c0b7bb
Replace manual backtracking by location information from the captured group (#4111) 2022-08-19 20:14:19 +02:00
JP Simard e8f738b39c
Automate producing release tarballs for Bazel (#4113) 2022-08-19 17:26:21 +00:00
Danny Mösch 6dc2ef937e
Add trigger markers to examples (#4110) 2022-08-18 22:15:11 +00:00
JP Simard 0e3f3de17f
Set CHANGELOG header back to 'Master'
And update bazel ref in readme to point to `0.49.0-rc.2`.
2022-08-18 16:35:21 -04:00
JP Simard dd55f59207
release 0.49.0-rc.2 2022-08-18 13:31:47 -04:00
JP Simard c1de2ef087
Fix changelog formatting 2022-08-18 13:27:35 -04:00
Kotaro Suto 78949c1b99
Fix broken correction for `explicit_init` rule (#4109) 2022-08-18 17:17:52 +00:00
chrisjf 1ef15f3bb7
Update documentation for multiline_arguments_brackets and multiline_literal_brackets (#4098)
- added two triggering examples that are a common style, to make it immediately obvious that they trigger the rules
- also fixed a spelling mistake throughout the examples for the multiline_literal_brackets rule ("Gryffindor" is now correctly spelt)
2022-08-18 09:53:52 +02:00
Kotaro Suto 3aa249b8a1
Fix first_where false negative (#4105) 2022-08-17 00:06:32 +00:00
JP Simard c2be5b18b4
Only check `sourcekitdFailed` for SourceKit-based rules (#4104)
And only warn once if it's disabled.

This check is expensive and as more rules move away from SourceKit to
SwiftSyntax, it's increasingly common for rules to not use SourceKit at
all.

In addition, SourceKit crashes used to be a lot more common but I
haven't seen one myself in quite a while.
2022-08-16 16:46:55 +00:00
JP Simard f6fc4727b6
Skip OSSCheck if binary hasn't changed (#4103) 2022-08-16 16:07:36 +00:00
JP Simard 73c88c3af0
Add `version --verbose` command (#4102)
Prints out something like this on macOS:

```
Version: 0.49.0-rc.1
Build ID: B43931F3-D096-3704-B41C-FC40673A3F96
```

This can be used to determine if two `swiftlint` executables are
identical.
2022-08-16 14:12:44 +00:00
806 changed files with 29521 additions and 19786 deletions

1
.bazelignore Normal file
View File

@ -0,0 +1 @@
.build

View File

@ -1 +1,15 @@
common --enable_bzlmod
try-import %workspace%/ci.bazelrc
try-import %workspace%/user.bazelrc
build --macos_minimum_os=12.0 --host_macos_minimum_os=12.0
build --disk_cache=~/.bazel_cache
build --experimental_remote_cache_compression
build --experimental_remote_build_event_upload=minimal
build --nolegacy_important_outputs
build --swiftcopt=-warnings-as-errors
build:release \
--compilation_mode=opt \
--features=swift.opt_uses_wmo

1
.bazelversion Normal file
View File

@ -0,0 +1 @@
6.2.0

3
.bcr/config.yml Normal file
View File

@ -0,0 +1,3 @@
fixedReleaser:
login: jpsim
email: jp@jpsim.com

View File

@ -0,0 +1,15 @@
{
"homepage": "https://github.com/realm/SwiftLint",
"maintainers": [
{
"email": "jp@jpsim.com",
"github": "jpsim",
"name": "JP Simard"
}
],
"repository": [
"github:realm/SwiftLint"
],
"versions": [],
"yanked_versions": {}
}

24
.bcr/presubmit.yml Normal file
View File

@ -0,0 +1,24 @@
shell_commands: &shell_commands
- "echo --- Downloading and extracting Swift $SWIFT_VERSION to $SWIFT_HOME"
- "mkdir $SWIFT_HOME"
- "curl https://download.swift.org/swift-${SWIFT_VERSION}-release/ubuntu2004/swift-${SWIFT_VERSION}-RELEASE/swift-${SWIFT_VERSION}-RELEASE-ubuntu20.04.tar.gz | tar xvz --strip-components=1 -C $SWIFT_HOME"
tasks:
verify_targets_linux:
name: Verify targets (Linux)
platform: ubuntu2004
environment:
CC: "clang"
SWIFT_VERSION: "5.7.2"
SWIFT_HOME: "$HOME/swift-$SWIFT_VERSION"
PATH: "$PATH:$SWIFT_HOME/usr/bin"
shell_commands: *shell_commands
build_flags:
- "--action_env=PATH"
build_targets:
- '@SwiftLint//:swiftlint'
verify_targets_macos:
name: Verify targets (macOS)
platform: macos
build_targets:
- '@SwiftLint//:swiftlint'

View File

@ -0,0 +1,5 @@
{
"url": "https://github.com/realm/SwiftLint/releases/download/{TAG}/bazel.tar.gz",
"integrity": "",
"strip_prefix": ""
}

View File

@ -5,13 +5,25 @@ steps:
- bazel build :swiftlint
- echo "+++ Test"
- bazel test --test_output=errors //Tests/...
- label: "SwiftPM"
commands:
- echo "+++ Test"
- swift test --parallel -Xswiftc -DDISABLE_FOCUSED_EXAMPLES
- label: "Danger"
commands:
- echo "--- Install Bundler"
- gem install bundler
- echo "--- Bundle Install"
- bundle install
- echo "+++ Run Danger"
- bundle exec danger --verbose
- label: "Analyze"
- label: "TSan Tests"
commands:
- echo "+++ Analyze"
- make analyze
- echo "+++ Test"
- bazel test --test_output=errors --build_tests_only --features=tsan --test_timeout=1000 //Tests/...
- label: "Sourcery"
commands:
- echo "+++ Run Sourcery"
- make --always-make sourcery
- echo "+++ Diff Files"
- git diff --quiet HEAD

View File

@ -1,4 +1,5 @@
*
!Plugins
!Source
!Tests
!Package.*

View File

@ -3,16 +3,16 @@ name: docker
on:
push:
branches:
- master
- main
tags:
- '*'
jobs:
build:
runs-on: ubuntu-latest
runs-on: ubuntu-20.04
steps:
- uses: actions/checkout@v2
- uses: actions/checkout@v3
- name: Extract DOCKER_TAG using tag name
if: startsWith(github.ref, 'refs/tags/')

6
.gitignore vendored
View File

@ -1,6 +1,5 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
@ -22,6 +21,7 @@ xcuserdata
*.moved-aside
*.xcuserstate
*.xcscmblueprint
default.profraw
## Obj-C/Swift specific
*.hmap
@ -44,6 +44,10 @@ benchmark_*
osscheck/
docs/
rule_docs/
bazel.tar.gz
bazel.tar.gz.sha256
ci.bazelrc
user.bazelrc
# Swift Package Manager
#

View File

@ -1,19 +1,20 @@
module: SwiftLintFramework
module: SwiftLintCore
author: JP Simard, SwiftLint Contributors
author_url: https://jpsim.com
root_url: https://realm.github.io/SwiftLint/
github_url: https://github.com/realm/SwiftLint
github_file_prefix: https://github.com/realm/SwiftLint/tree/master
github_file_prefix: https://github.com/realm/SwiftLint/tree/main
swift_build_tool: spm
theme: fullwidth
clean: true
copyright: '© 2020 [JP Simard](https://jpsim.com) under MIT.'
copyright: '© 2023 [JP Simard](https://jpsim.com) under MIT.'
documentation: rule_docs/*.md
hide_unlisted_documentation: true
custom_categories_unlisted_prefix: ''
exclude:
- Source/SwiftLintFramework/Rules/**/*.swift
# TODO: Document extensions
- Source/SwiftLintCore/Extensions/*.swift
custom_categories:
- name: Rules
children:
@ -22,7 +23,7 @@ custom_categories:
children:
- CSVReporter
- CheckstyleReporter
- CodeclimateReporter
- CodeClimateReporter
- EmojiReporter
- GitHubActionsLoggingReporter
- HTMLReporter

View File

@ -1,4 +1,5 @@
/// The rule list containing all available rules built into SwiftLint.
public let primaryRuleList = RuleList(rules: [
public let builtInRules: [Rule.Type] = [
{% for rule in types.structs where rule.name|hasSuffix:"Rule" or rule.name|hasSuffix:"Rules" %} {{ rule.name }}.self{% if not forloop.last %},{% endif %}
{% endfor %}] + extraRules())
{% endfor %}]

View File

@ -1,11 +1,14 @@
import SwiftLintFramework
import XCTest
@testable import SwiftLintBuiltInRules
@_spi(TestHelper)
@testable import SwiftLintCore
import SwiftLintTestHelpers
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable file_length single_test_class type_name
{% for rule in types.structs %}
{% if rule.name|hasSuffix:"Rule" %}
class {{ rule.name }}GeneratedTests: XCTestCase {
class {{ rule.name }}GeneratedTests: SwiftLintTestCase {
func testWithDefaultConfiguration() {
verifyRule({{ rule.name }}.description)
}

View File

@ -0,0 +1,7 @@
/// The reporters list containing all the reporters built into SwiftLint.
public let reportersList: [Reporter.Type] = [
{% for reporter in types.structs where reporter.name|hasSuffix:"Reporter" %}
{{ reporter.name }}.self{% if not forloop.last %},{% endif %}
{% endfor %}
]

View File

@ -1,4 +1,5 @@
included:
- Plugins
- Source
- Tests
excluded:
@ -7,76 +8,55 @@ analyzer_rules:
- unused_declaration
- unused_import
opt_in_rules:
- all
disabled_rules:
- anonymous_argument_in_multiline_closure
- anyobject_protocol
- array_init
- attributes
- closure_end_indentation
- closure_spacing
- collection_alignment
- contains_over_filter_count
- contains_over_filter_is_empty
- contains_over_first_not_nil
- contains_over_range_nil_comparison
- discouraged_none_name
- discouraged_object_literal
- empty_collection_literal
- empty_count
- empty_string
- empty_xctest_method
- enum_case_associated_values_count
- explicit_init
- extension_access_modifier
- fallthrough
- fatal_error_message
- file_header
- file_name
- first_where
- flatmap_over_map_reduce
- identical_operands
- joined_default_parameter
- last_where
- legacy_multiple
- literal_expression_end_indentation
- lower_acl_than_parent
- modifier_order
- nimble_operator
- nslocalizedstring_key
- number_separator
- object_literal
- operator_usage_whitespace
- overridden_super_call
- override_in_extension
- pattern_matching_keywords
- closure_body_length
- conditional_returns_on_newline
- convenience_type
- discouraged_optional_collection
- explicit_acl
- explicit_enum_raw_value
- explicit_top_level_acl
- explicit_type_interface
- file_types_order
- force_unwrapping
- function_default_parameter_at_end
- implicit_return
- implicitly_unwrapped_optional
- indentation_width
- inert_defer
- missing_docs
- multiline_arguments
- multiline_arguments_brackets
- multiline_function_chains
- multiline_literal_brackets
- multiline_parameters
- multiline_parameters_brackets
- no_extension_access_modifier
- no_fallthrough_only
- no_grouping_extension
- no_magic_numbers
- prefer_nimble
- prefer_self_in_static_references
- prefer_self_type_over_type_of_self
- private_action
- private_outlet
- prohibited_interface_builder
- prohibited_super_call
- quick_discouraged_call
- quick_discouraged_focused_test
- quick_discouraged_pending_test
- reduce_into
- redundant_nil_coalescing
- redundant_type_annotation
- return_value_from_void_function
- single_test_class
- sorted_first_last
- sorted_imports
- static_operator
- strong_iboutlet
- test_case_accessibility
- toggle_bool
- unavailable_function
- unneeded_parentheses_in_closure_argument
- unowned_variable_capture
- untyped_error_in_catch
- vertical_parameter_alignment_on_call
- vertical_whitespace_closing_braces
- vertical_whitespace_opening_braces
- xct_specific_matcher
- yoda_condition
- prefixed_toplevel_constant
- redundant_self_in_closure
- required_deinit
- self_binding
- sorted_enum_cases
- strict_fileprivate
- superfluous_else
- switch_case_on_newline
- todo
- trailing_closure
- type_contents_order
- unused_capture_list
- vertical_whitespace_between_cases
attributes:
always_on_line_above:
- "@OptionGroup"
identifier_name:
excluded:
- id
@ -85,15 +65,27 @@ number_separator:
minimum_length: 5
file_name:
excluded:
- Exports.swift
- GeneratedTests.swift
- SwiftSyntax+SwiftLint.swift
- TestHelpers.swift
balanced_xctest_lifecycle: &unit_test_configuration
test_parent_classes:
- SwiftLintTestCase
- XCTestCase
empty_xctest_method: *unit_test_configuration
single_test_class: *unit_test_configuration
function_body_length: 60
type_body_length: 400
custom_rules:
rule_id:
included: Source/SwiftLintFramework/Rules/.+/\w+\.swift
included: Source/SwiftLintBuiltInRules/Rules/.+/\w+\.swift
name: Rule ID
message: Rule IDs must be all lowercase, snake case and not end with `rule`
regex: identifier:\s*("\w+_rule"|"\S*[^a-z_]\S*")
regex: ^\s+identifier:\s*("\w+_rule"|"\S*[^a-z_]\S*")
severity: error
fatal_error:
name: Fatal Error
@ -109,3 +101,8 @@ custom_rules:
message: Rule Test Function mustn't end with `rule`
regex: func\s*test\w+(r|R)ule\(\)
severity: error
unused_import:
always_keep_imports:
- SwiftSyntaxBuilder # we can't detect uses of string interpolation of swift syntax nodes
- SwiftLintFramework # now that this is a wrapper around other modules, don't treat as unused

179
BUILD
View File

@ -1,36 +1,78 @@
load("@build_bazel_rules_apple//apple:apple.bzl", "apple_universal_binary")
load(
"@build_bazel_rules_swift//swift:swift.bzl",
"swift_binary",
"swift_library",
)
load(
"@com_github_buildbuddy_io_rules_xcodeproj//xcodeproj:xcodeproj.bzl",
"@rules_xcodeproj//xcodeproj:defs.bzl",
"xcode_schemes",
"xcodeproj",
)
# Targets
swift_library(
name = "SwiftLintCore",
srcs = glob(["Source/SwiftLintCore/**/*.swift"]),
module_name = "SwiftLintCore",
visibility = ["//visibility:public"],
deps = [
"@SwiftSyntax//:SwiftIDEUtils_opt",
"@SwiftSyntax//:SwiftOperators_opt",
"@SwiftSyntax//:SwiftParserDiagnostics_opt",
"@SwiftSyntax//:SwiftSyntaxBuilder_opt",
"@SwiftSyntax//:SwiftSyntax_opt",
"@com_github_jpsim_sourcekitten//:SourceKittenFramework",
"@sourcekitten_com_github_jpsim_yams//:Yams",
"@swiftlint_com_github_scottrhoyt_swifty_text_table//:SwiftyTextTable",
] + select({
"@platforms//os:linux": ["@com_github_krzyzanowskim_cryptoswift//:CryptoSwift"],
"//conditions:default": [":DyldWarningWorkaround"],
}),
)
swift_library(
name = "SwiftLintBuiltInRules",
srcs = glob(["Source/SwiftLintBuiltInRules/**/*.swift"]),
module_name = "SwiftLintBuiltInRules",
visibility = ["//visibility:public"],
deps = [
":SwiftLintCore",
],
)
swift_library(
name = "SwiftLintExtraRules",
srcs = [
"Source/SwiftLintExtraRules/Exports.swift",
"@swiftlint_extra_rules//:extra_rules",
],
module_name = "SwiftLintExtraRules",
visibility = ["//visibility:public"],
deps = [
":SwiftLintCore",
],
)
swift_library(
name = "SwiftLintFramework",
srcs = glob(
["Source/SwiftLintFramework/**/*.swift"],
exclude = ["Source/SwiftLintFramework/Rules/ExcludedFromBazel/ExtraRules.swift"],
) + ["@swiftlint_extra_rules//:extra_rules"],
),
module_name = "SwiftLintFramework",
visibility = ["//visibility:public"],
deps = [
"@com_github_jpsim_sourcekitten//:SourceKittenFramework",
"@com_github_keith_swift_syntax//:SwiftSyntax",
"@com_github_keith_swift_syntax//:SwiftSyntaxParser",
"@sourcekitten_com_github_jpsim_yams//:Yams",
] + select({
"@platforms//os:linux": ["@com_github_krzyzanowskim_cryptoswift//:CryptoSwift"],
"//conditions:default": [],
}),
":SwiftLintBuiltInRules",
":SwiftLintCore",
":SwiftLintExtraRules",
],
)
swift_binary(
name = "swiftlint",
swift_library(
name = "swiftlint.library",
srcs = glob(["Source/swiftlint/**/*.swift"]),
module_name = "swiftlint",
visibility = ["//visibility:public"],
deps = [
":SwiftLintFramework",
@ -40,35 +82,130 @@ swift_binary(
],
)
filegroup(
name = "SwiftLintConfig",
srcs = [".swiftlint.yml"],
visibility = ["//Tests:__subpackages__"],
swift_binary(
name = "swiftlint",
visibility = ["//visibility:public"],
deps = [
":swiftlint.library",
],
)
apple_universal_binary(
name = "universal_swiftlint",
binary = ":swiftlint",
forced_cpus = [
"x86_64",
"arm64",
],
minimum_os_version = "12.0",
platform_type = "macos",
visibility = ["//visibility:public"],
)
filegroup(
name = "SourceFilesToLint",
srcs = glob(["Source/**"]),
name = "DyldWarningWorkaroundSources",
srcs = [
"Source/DyldWarningWorkaround/DyldWarningWorkaround.c",
"Source/DyldWarningWorkaround/include/objc_dupclass.h",
],
)
cc_library(
name = "DyldWarningWorkaround",
srcs = ["//:DyldWarningWorkaroundSources"],
includes = ["Source/DyldWarningWorkaround/include"],
alwayslink = True,
)
# Linting
filegroup(
name = "LintInputs",
srcs = glob(["Source/**/*.swift"]) + [
".swiftlint.yml",
"//Tests:SwiftLintFrameworkTestsData",
],
visibility = ["//Tests:__subpackages__"],
)
# Release
filegroup(
name = "release_files",
srcs = [
"BUILD",
"LICENSE",
"MODULE.bazel",
"//:DyldWarningWorkaroundSources",
"//:LintInputs",
"//Tests:BUILD",
"//bazel:release_files",
],
)
# TODO: Use rules_pkg
genrule(
name = "release",
srcs = [":release_files"],
outs = [
"bazel.tar.gz",
"bazel.tar.gz.sha256",
],
cmd = """\
set -euo pipefail
outs=($(OUTS))
COPYFILE_DISABLE=1 tar czvfh "$${outs[0]}" \
--exclude ^bazel-out/ \
--exclude ^external/ \
*
shasum -a 256 "$${outs[0]}" > "$${outs[1]}"
""",
)
# Xcode Integration
xcodeproj(
name = "xcodeproj",
build_mode = "bazel",
project_name = "SwiftLint",
schemes = [
xcode_schemes.scheme(
name = "SwiftLint",
launch_action = xcode_schemes.launch_action("//:swiftlint"),
launch_action = xcode_schemes.launch_action(
"swiftlint",
args = [
"--progress",
],
),
test_action = xcode_schemes.test_action([
"//Tests:CLITests",
"//Tests:SwiftLintFrameworkTests",
"//Tests:GeneratedTests",
"//Tests:IntegrationTests",
"//Tests:ExtraRulesTests",
]),
),
],
top_level_targets = [
"//:swiftlint",
"//Tests:CLITests",
"//Tests:SwiftLintFrameworkTests",
"//Tests:GeneratedTests",
"//Tests:IntegrationTests",
"//Tests:ExtraRulesTests",
],
)
# Analyze
sh_test(
name = "analyze",
srcs = ["//tools:test-analyze.sh"],
data = [
"Package.resolved",
"Package.swift",
":LintInputs",
":swiftlint",
],
)

File diff suppressed because it is too large Load Diff

View File

@ -1,8 +1,14 @@
## Tutorial
If you'd like to write a SwiftLint rule but aren't sure how to start,
please watch and follow along with
[this video tutorial](https://vimeo.com/819268038).
## Pull Requests
All changes, no matter how trivial, must be done via pull request. Commits
should never be made directly on the `master` branch. Prefer rebasing over
merging `master` into your PR branch to update it and resolve conflicts.
should never be made directly on the `main` branch. Prefer rebasing over
merging `main` into your PR branch to update it and resolve conflicts.
_If you have commit access to SwiftLint and believe your change to be trivial
and not worth waiting for review, you may open a pull request and merge
@ -58,7 +64,7 @@ $ make docker_test
## Rules
New rules should be added in the `Source/SwiftLintFramework/Rules` directory.
New rules should be added in the `Source/SwiftLintBuiltInRules/Rules` directory.
Rules should conform to either the `Rule` or `ASTRule` protocols.
@ -98,11 +104,11 @@ configuration object via the `configuration` property:
* If none of the provided `RuleConfiguration`s are applicable, you can create one
specifically for your rule.
See [`ForceCastRule`](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/Idiomatic/ForceCastRule.swift)
See [`ForceCastRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintBuiltInRules/Rules/Idiomatic/ForceCastRule.swift)
for a rule that allows severity configuration,
[`FileLengthRule`](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/Metrics/FileLengthRule.swift)
[`FileLengthRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintBuiltInRules/Rules/Metrics/FileLengthRule.swift)
for a rule that has multiple severity levels,
[`IdentifierNameRule`](https://github.com/realm/SwiftLint/blob/master/Source/SwiftLintFramework/Rules/Style/IdentifierNameRule.swift)
[`IdentifierNameRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintBuiltInRules/Rules/Style/IdentifierNameRule.swift)
for a rule that allows name evaluation configuration:
``` yaml
@ -170,9 +176,9 @@ To bring up a new Buildkite worker from MacStadium:
1. Change account password
1. Update macOS to the latest version
1. Install Homebrew: https://brew.sh
1. Install Buildkite agent and other tools via Homebrew: `brew install buildkite/buildkite/buildkite-agent aria2 htop`
1. Install the xcodes CLI by downloading the zip and moving it to `/usr/local/bin`: https://github.com/RobotsAndPencils/xcodes/releases
1. Install latest Xcode version: `xcodes install --latest`
1. Install bundler: `sudo gem install bundler`
1. Add `DANGER_GITHUB_API_TOKEN` to `/opt/homebrew/etc/buildkite-agent/hooks/environment`
1. Configure and launch buildkite agent: `brew info buildkite-agent` / https://buildkite.com/organizations/swiftlint/agents#setup-macos
1. Install Buildkite agent and other tools via Homebrew:
`brew install aria2 bazelisk htop buildkite/buildkite/buildkite-agent robotsandpencils/made/xcodes`
1. Install latest Xcode version: `xcodes update && xcodes install 14.0.0`
1. Add `DANGER_GITHUB_API_TOKEN` and `HOME` to `/opt/homebrew/etc/buildkite-agent/hooks/environment`
1. Configure and launch buildkite agent: `brew info buildkite-agent` /
https://buildkite.com/organizations/swiftlint/agents#setup-macos

View File

@ -14,13 +14,13 @@ modified_files = git.modified_files + git.added_files
# including in a CHANGELOG for example
has_app_changes = !modified_files.grep(/Source/).empty?
has_test_changes = !modified_files.grep(/Tests/).empty?
has_danger_changes = !modified_files.grep(/Dangerfile|script\/oss-check|Gemfile/).empty?
has_danger_changes = !modified_files.grep(/Dangerfile|tools\/oss-check|Gemfile/).empty?
has_package_changes = !modified_files.grep(/Package\.swift/).empty?
has_bazel_changes = !modified_files.grep(/WORKSPACE|bazel\/|BUILD/).empty?
has_bazel_changes = !modified_files.grep(/\.bazelrc|\.bazelversion|WORKSPACE|bazel\/|BUILD|MODULE\.bazel/).empty?
# Add a CHANGELOG entry for app changes
if !modified_files.include?('CHANGELOG.md') && has_app_changes
warn("If this is a user-facing change, please include a CHANGELOG entry to credit yourself! \nYou can find it at [CHANGELOG.md](https://github.com/realm/SwiftLint/blob/master/CHANGELOG.md).")
warn("If this is a user-facing change, please include a CHANGELOG entry to credit yourself! \nYou can find it at [CHANGELOG.md](https://github.com/realm/SwiftLint/blob/main/CHANGELOG.md).")
markdown <<-MARKDOWN
Here's an example of your CHANGELOG entry:
```markdown
@ -51,7 +51,8 @@ end
file = Tempfile.new('violations')
Open3.popen3("script/oss-check -v 2> #{file.path}") do |_, stdout, _, _|
force_flag = has_danger_changes ? "--force" : ""
Open3.popen3("tools/oss-check -v #{force_flag} 2> #{file.path}") do |_, stdout, _, _|
while char = stdout.getc
print char
end

View File

@ -1,6 +1,6 @@
# Explicitly specify `focal` because `swift:latest` does not use `ubuntu:latest`.
ARG BUILDER_IMAGE=swift:focal
ARG RUNTIME_IMAGE=ubuntu:focal
# Explicitly specify `jammy` to keep the Swift & Ubuntu images in sync.
ARG BUILDER_IMAGE=swift:jammy
ARG RUNTIME_IMAGE=ubuntu:jammy
# builder image
FROM ${BUILDER_IMAGE} AS builder
@ -9,18 +9,16 @@ RUN apt-get update && apt-get install -y \
libxml2-dev \
&& rm -r /var/lib/apt/lists/*
WORKDIR /workdir/
COPY Plugins Plugins/
COPY Source Source/
COPY Tests Tests/
COPY Package.* ./
RUN ln -s /usr/lib/swift/_InternalSwiftSyntaxParser .
RUN swift package update
ARG SWIFT_FLAGS="-c release -Xswiftc -static-stdlib -Xlinker -lCFURLSessionInterface -Xlinker -lCFXMLInterface -Xlinker -lcurl -Xlinker -lxml2 -Xswiftc -I. -Xlinker -fuse-ld=lld -Xlinker -L/usr/lib/swift/linux"
RUN swift build $SWIFT_FLAGS
RUN swift build $SWIFT_FLAGS --product swiftlint
RUN mkdir -p /executables
RUN for executable in $(swift package completion-tool list-executables); do \
install -v `swift build $SWIFT_FLAGS --show-bin-path`/$executable /executables; \
done
RUN install -v `swift build $SWIFT_FLAGS --show-bin-path`/swiftlint /executables
# runtime image
FROM ${RUNTIME_IMAGE}
@ -32,9 +30,10 @@ RUN apt-get update && apt-get install -y \
COPY --from=builder /usr/lib/libsourcekitdInProc.so /usr/lib
COPY --from=builder /usr/lib/swift/linux/libBlocksRuntime.so /usr/lib
COPY --from=builder /usr/lib/swift/linux/libdispatch.so /usr/lib
COPY --from=builder /usr/lib/swift/linux/lib_InternalSwiftSyntaxParser.so /usr/lib
COPY --from=builder /usr/lib/swift/linux/libswiftCore.so /usr/lib
COPY --from=builder /executables/* /usr/bin
RUN swiftlint version
RUN echo "_ = 0" | swiftlint --use-stdin
CMD ["swiftlint"]

View File

@ -1,16 +1,15 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.5)
CFPropertyList (3.0.6)
rexml
activesupport (6.1.5.1)
activesupport (7.0.4.3)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
zeitwerk (~> 2.3)
addressable (2.8.0)
public_suffix (>= 2.0.2, < 5.0)
addressable (2.8.4)
public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
json (>= 1.5.1)
@ -20,15 +19,15 @@ GEM
cork
nap
open4 (~> 1.3)
cocoapods (1.11.3)
cocoapods (1.12.1)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.11.3)
cocoapods-core (= 1.12.1)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.4.0, < 2.0)
cocoapods-downloader (>= 1.6.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.4.0, < 2.0)
cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
@ -36,10 +35,10 @@ GEM
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 1.0, < 3.0)
ruby-macho (>= 2.3.0, < 3.0)
xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.11.3)
activesupport (>= 5.0, < 7)
cocoapods-core (1.12.1)
activesupport (>= 5.0, < 8)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
@ -58,60 +57,42 @@ GEM
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.1.10)
concurrent-ruby (1.2.2)
cork (0.3.0)
colored2 (~> 3.1)
danger (8.6.1)
danger (9.2.0)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
cork (~> 0.1)
faraday (>= 0.9.0, < 2.0)
faraday (>= 0.9.0, < 3.0)
faraday-http-cache (~> 2.0)
git (~> 1.7)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
no_proxy_fix
octokit (~> 4.7)
octokit (~> 5.0)
terminal-table (>= 1, < 4)
escape (0.0.4)
ethon (0.15.0)
ethon (0.16.0)
ffi (>= 1.15.0)
faraday (1.10.0)
faraday-em_http (~> 1.0)
faraday-em_synchrony (~> 1.0)
faraday-excon (~> 1.1)
faraday-httpclient (~> 1.0)
faraday-multipart (~> 1.0)
faraday-net_http (~> 1.0)
faraday-net_http_persistent (~> 1.0)
faraday-patron (~> 1.0)
faraday-rack (~> 1.0)
faraday-retry (~> 1.0)
faraday (2.7.4)
faraday-net_http (>= 2.0, < 3.1)
ruby2_keywords (>= 0.0.4)
faraday-em_http (1.0.0)
faraday-em_synchrony (1.0.0)
faraday-excon (1.1.0)
faraday-http-cache (2.2.0)
faraday-http-cache (2.4.1)
faraday (>= 0.8)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.3)
multipart-post (>= 1.2, < 3)
faraday-net_http (1.0.1)
faraday-net_http_persistent (1.2.0)
faraday-patron (1.0.0)
faraday-rack (1.0.0)
faraday-retry (1.0.3)
faraday-net_http (3.0.2)
ffi (1.15.5)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
git (1.11.0)
git (1.18.0)
addressable (~> 2.8)
rchardet (~> 1.8)
httpclient (2.8.3)
i18n (1.10.0)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
jazzy (0.14.2)
jazzy (0.14.3)
cocoapods (~> 1.5)
mustache (~> 1.1)
open4 (~> 1.3)
@ -121,58 +102,56 @@ GEM
sassc (~> 2.1)
sqlite3 (~> 1.3)
xcinvoke (~> 0.3.0)
json (2.6.1)
json (2.6.3)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liferaft (0.0.6)
minitest (5.15.0)
minitest (5.18.0)
molinillo (0.8.0)
multipart-post (2.1.1)
mustache (1.1.1)
nanaimo (0.3.0)
nap (1.1.0)
netrc (0.11.0)
no_proxy_fix (0.1.2)
octokit (4.22.0)
faraday (>= 0.9)
sawyer (~> 0.8.0, >= 0.5.3)
octokit (5.6.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
open4 (1.3.4)
public_suffix (4.0.7)
rchardet (1.8.0)
redcarpet (3.5.1)
redcarpet (3.6.0)
rexml (3.2.5)
rouge (3.28.0)
rouge (3.30.0)
ruby-macho (2.5.1)
ruby2_keywords (0.0.5)
sassc (2.4.0)
ffi (~> 1.9)
sawyer (0.8.2)
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (> 0.8, < 2.0)
sqlite3 (1.4.2)
faraday (>= 0.17.3, < 3)
sqlite3 (1.6.2-arm64-darwin)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (2.0.4)
tzinfo (2.0.6)
concurrent-ruby (~> 1.0)
unicode-display_width (2.1.0)
unicode-display_width (2.4.2)
xcinvoke (0.3.0)
liferaft (~> 0.0.6)
xcodeproj (1.21.0)
xcodeproj (1.22.0)
CFPropertyList (>= 2.3.3, < 4.0)
atomos (~> 0.1.3)
claide (>= 1.0.2, < 2.0)
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
zeitwerk (2.5.4)
PLATFORMS
arm64-darwin-21
ruby
arm64-darwin-22
DEPENDENCIES
cocoapods
@ -180,4 +159,4 @@ DEPENDENCIES
jazzy
BUNDLED WITH
2.3.8
2.4.12

27
MODULE.bazel Normal file
View File

@ -0,0 +1,27 @@
module(
name = "swiftlint",
version = "0.52.2",
compatibility_level = 1,
repo_name = "SwiftLint",
)
bazel_dep(name = "bazel_skylib", version = "1.4.1", dev_dependency = True)
bazel_dep(name = "platforms", version = "0.0.6")
bazel_dep(name = "rules_apple", version = "2.3.0", repo_name = "build_bazel_rules_apple")
bazel_dep(name = "rules_swift", version = "1.8.0", repo_name = "build_bazel_rules_swift")
bazel_dep(name = "rules_xcodeproj", version = "1.7.0")
bazel_dep(name = "sourcekitten", version = "0.34.1", repo_name = "com_github_jpsim_sourcekitten")
bazel_dep(name = "swift_argument_parser", version = "1.2.1", repo_name = "sourcekitten_com_github_apple_swift_argument_parser")
bazel_dep(name = "yams", version = "5.0.5", repo_name = "sourcekitten_com_github_jpsim_yams")
swiftlint_repos = use_extension("//bazel:repos.bzl", "swiftlint_repos_bzlmod")
use_repo(
swiftlint_repos,
"SwiftSyntax",
"com_github_johnsundell_collectionconcurrencykit",
"com_github_krzyzanowskim_cryptoswift",
"swiftlint_com_github_scottrhoyt_swifty_text_table",
)
extra_rules = use_extension("//bazel:extensions.bzl", "extra_rules")
use_repo(extra_rules, "swiftlint_extra_rules")

View File

@ -7,10 +7,7 @@ XCODEFLAGS=-scheme 'swiftlint' \
OTHER_LDFLAGS=-Wl,-headerpad_max_install_names
SWIFT_BUILD_FLAGS=--configuration release -Xlinker -dead_strip
UNAME=$(shell uname)
SWIFTLINT_EXECUTABLE_X86=$(shell swift build $(SWIFT_BUILD_FLAGS) --arch x86_64 --show-bin-path)/swiftlint
SWIFTLINT_EXECUTABLE_ARM64=$(shell swift build $(SWIFT_BUILD_FLAGS) --arch arm64 --show-bin-path)/swiftlint
SWIFTLINT_EXECUTABLE_PARENT=.build/universal
SWIFTLINT_EXECUTABLE=$(SWIFTLINT_EXECUTABLE_PARENT)/swiftlint
@ -27,21 +24,25 @@ LICENSE_PATH="$(shell pwd)/LICENSE"
OUTPUT_PACKAGE=SwiftLint.pkg
VERSION_STRING=$(shell ./script/get-version)
VERSION_STRING=$(shell ./tools/get-version)
.PHONY: all clean build install package test uninstall docs
all: build
sourcery: Source/SwiftLintFramework/Models/PrimaryRuleList.swift Tests/SwiftLintFrameworkTests/GeneratedTests.swift
sourcery: Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift Source/SwiftLintCore/Models/ReportersList.swift Tests/GeneratedTests/GeneratedTests.swift
Source/SwiftLintFramework/Models/PrimaryRuleList.swift: Source/SwiftLintFramework/Rules/**/*.swift .sourcery/PrimaryRuleList.stencil
sourcery --sources Source/SwiftLintFramework/Rules --templates .sourcery/PrimaryRuleList.stencil --output .sourcery
mv .sourcery/PrimaryRuleList.generated.swift Source/SwiftLintFramework/Models/PrimaryRuleList.swift
Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift: Source/SwiftLintBuiltInRules/Rules/**/*.swift .sourcery/BuiltInRules.stencil
./tools/sourcery --sources Source/SwiftLintBuiltInRules/Rules --templates .sourcery/BuiltInRules.stencil --output .sourcery
mv .sourcery/BuiltInRules.generated.swift Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift
Tests/SwiftLintFrameworkTests/GeneratedTests.swift: Source/SwiftLintFramework/Rules/**/*.swift .sourcery/GeneratedTests.stencil
sourcery --sources Source/SwiftLintFramework/Rules --templates .sourcery/GeneratedTests.stencil --output .sourcery
mv .sourcery/GeneratedTests.generated.swift Tests/SwiftLintFrameworkTests/GeneratedTests.swift
Source/SwiftLintCore/Models/ReportersList.swift: Source/SwiftLintCore/Reporters/*.swift .sourcery/ReportersList.stencil
./tools/sourcery --sources Source/SwiftLintCore/Reporters --templates .sourcery/ReportersList.stencil --output .sourcery
mv .sourcery/ReportersList.generated.swift Source/SwiftLintCore/Models/ReportersList.swift
Tests/GeneratedTests/GeneratedTests.swift: Source/SwiftLint*/Rules/**/*.swift .sourcery/GeneratedTests.stencil
./tools/sourcery --sources Source/SwiftLintCore/Rules --sources Source/SwiftLintBuiltInRules/Rules --templates .sourcery/GeneratedTests.stencil --output .sourcery
mv .sourcery/GeneratedTests.generated.swift Tests/GeneratedTests/GeneratedTests.swift
test: clean_xcode
$(BUILD_TOOL) $(XCODEFLAGS) test
@ -62,25 +63,18 @@ analyze_autocorrect: write_xcodebuild_log
clean:
rm -f "$(OUTPUT_PACKAGE)"
rm -rf "$(TEMPORARY_FOLDER)"
rm -f "./*.zip"
rm -f "./*.zip" "bazel.tar.gz" "bazel.tar.gz.sha256"
swift package clean
clean_xcode:
$(BUILD_TOOL) $(XCODEFLAGS) -configuration Test clean
build_x86_64:
swift build $(SWIFT_BUILD_FLAGS) --arch x86_64
build_arm64:
swift build $(SWIFT_BUILD_FLAGS) --arch arm64
build: clean build_x86_64 build_arm64
# Need to build for each arch independently to work around https://bugs.swift.org/browse/SR-15802
mkdir -p $(SWIFTLINT_EXECUTABLE_PARENT)
lipo -create -output \
"$(SWIFTLINT_EXECUTABLE)" \
"$(SWIFTLINT_EXECUTABLE_X86)" \
"$(SWIFTLINT_EXECUTABLE_ARM64)"
build:
mkdir -p "$(SWIFTLINT_EXECUTABLE_PARENT)"
bazel build --config release universal_swiftlint
$(eval SWIFTLINT_BINARY := $(shell bazel cquery --config release --output=files universal_swiftlint))
mv "$(SWIFTLINT_BINARY)" "$(SWIFTLINT_EXECUTABLE)"
chmod +w "$(SWIFTLINT_EXECUTABLE)"
strip -rSTX "$(SWIFTLINT_EXECUTABLE)"
build_with_disable_sandbox:
@ -109,7 +103,7 @@ portable_zip: installables
spm_artifactbundle_macos: installables
mkdir -p "$(ARTIFACT_BUNDLE_PATH)/swiftlint-$(VERSION_STRING)-macos/bin"
sed 's/__VERSION__/$(VERSION_STRING)/g' script/info-macos.json.template > "$(ARTIFACT_BUNDLE_PATH)/info.json"
sed 's/__VERSION__/$(VERSION_STRING)/g' tools/info-macos.json.template > "$(ARTIFACT_BUNDLE_PATH)/info.json"
cp -f "$(SWIFTLINT_EXECUTABLE)" "$(ARTIFACT_BUNDLE_PATH)/swiftlint-$(VERSION_STRING)-macos/bin"
cp -f "$(LICENSE_PATH)" "$(ARTIFACT_BUNDLE_PATH)"
(cd "$(TEMPORARY_FOLDER)"; zip -yr - "SwiftLintBinary.artifactbundle") > "./SwiftLintBinary-macos.artifactbundle.zip"
@ -123,10 +117,11 @@ zip_linux: docker_image
zip_linux_release:
$(eval TMP_FOLDER := $(shell mktemp -d))
docker run "ghcr.io/realm/swiftlint:$(VERSION_STRING)" cat /usr/bin/swiftlint > "$(TMP_FOLDER)/swiftlint"
docker run --platform linux/amd64 "ghcr.io/realm/swiftlint:$(VERSION_STRING)" cat /usr/bin/swiftlint > "$(TMP_FOLDER)/swiftlint"
chmod +x "$(TMP_FOLDER)/swiftlint"
cp -f "$(LICENSE_PATH)" "$(TMP_FOLDER)"
(cd "$(TMP_FOLDER)"; zip -yr - "swiftlint" "LICENSE") > "./swiftlint_linux.zip"
gh release upload "$(VERSION_STRING)" swiftlint_linux.zip
package: build
$(eval PACKAGE_ROOT := $(shell mktemp -d))
@ -138,13 +133,15 @@ package: build
--version "$(VERSION_STRING)" \
"$(OUTPUT_PACKAGE)"
release: package portable_zip spm_artifactbundle_macos zip_linux_release
bazel_release:
bazel build :release
mv bazel-bin/bazel.tar.gz bazel-bin/bazel.tar.gz.sha256 .
docker_image:
docker build --platform linux/amd64 --force-rm --tag swiftlint .
docker_test:
docker run -v `pwd`:`pwd` -w `pwd` --name swiftlint --rm swift:5.6-focal swift test --parallel
docker run -v `pwd`:`pwd` -w `pwd` --name swiftlint --rm swift:5.7-focal swift test --parallel
docker_htop:
docker run --platform linux/amd64 -it --rm --pid=container:swiftlint terencewestphal/htop || reset
@ -155,8 +152,8 @@ display_compilation_time:
publish:
brew update && brew bump-formula-pr --tag=$(shell git describe --tags) --revision=$(shell git rev-parse HEAD) swiftlint
# Workaround for https://github.com/CocoaPods/CocoaPods/issues/11185
arch -arch x86_64 pod trunk push SwiftLint.podspec
bundle install
bundle exec pod trunk push SwiftLint.podspec
docs:
swift run swiftlint generate-docs
@ -166,18 +163,30 @@ docs:
get_version:
@echo "$(VERSION_STRING)"
push_version:
release:
ifneq ($(strip $(shell git status --untracked-files=no --porcelain 2>/dev/null)),)
$(error git state is not clean)
endif
$(eval NEW_VERSION_AND_NAME := $(filter-out $@,$(MAKECMDGOALS)))
$(eval NEW_VERSION := $(shell echo $(NEW_VERSION_AND_NAME) | sed 's/:.*//' ))
@sed -i '' 's/## Master/## $(NEW_VERSION_AND_NAME)/g' CHANGELOG.md
@sed 's/__VERSION__/$(NEW_VERSION)/g' script/Version.swift.template > Source/SwiftLintFramework/Models/Version.swift
@sed -i '' 's/## Main/## $(NEW_VERSION_AND_NAME)/g' CHANGELOG.md
@sed 's/__VERSION__/$(NEW_VERSION)/g' tools/Version.swift.template > Source/SwiftLintCore/Models/Version.swift
@sed -e '3s/.*/ version = "$(NEW_VERSION)",/' -i '' MODULE.bazel
make clean
make package
make bazel_release
make portable_zip
make spm_artifactbundle_macos
./tools/update-artifact-bundle.sh "$(NEW_VERSION)"
git commit -a -m "release $(NEW_VERSION)"
git tag -a $(NEW_VERSION) -m "$(NEW_VERSION_AND_NAME)"
git push origin HEAD
git push origin $(NEW_VERSION)
./tools/create-github-release.sh "$(NEW_VERSION)"
make publish
./tools/add-new-changelog-section.sh
git commit -a -m "Add new changelog section"
git push origin HEAD
%:
@:

View File

@ -1,70 +1,77 @@
{
"object": {
"pins": [
{
"package": "CollectionConcurrencyKit",
"repositoryURL": "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
"state": {
"branch": null,
"revision": "b4f23e24b5a1bff301efc5e70871083ca029ff95",
"version": "0.2.0"
}
},
{
"package": "SourceKitten",
"repositoryURL": "https://github.com/jpsim/SourceKitten.git",
"state": {
"branch": null,
"revision": "b5f9bb749057dd396e93f97956bef64672bc2a04",
"version": "0.33.0"
}
},
{
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser.git",
"state": {
"branch": null,
"revision": "df9ee6676cd5b3bf5b330ec7568a5644f547201b",
"version": "1.1.3"
}
},
{
"package": "SwiftSyntax",
"repositoryURL": "https://github.com/apple/swift-syntax.git",
"state": {
"branch": null,
"revision": "0b6c22b97f8e9320bca62e82cdbee601cf37ad3f",
"version": "0.50600.1"
}
},
{
"package": "SwiftyTextTable",
"repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git",
"state": {
"branch": null,
"revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
"version": "0.9.0"
}
},
{
"package": "SWXMLHash",
"repositoryURL": "https://github.com/drmohundro/SWXMLHash.git",
"state": {
"branch": null,
"revision": "4d0f62f561458cbe1f732171e625f03195151b60",
"version": "7.0.1"
}
},
{
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "01835dc202670b5bb90d07f3eae41867e9ed29f6",
"version": "5.0.1"
}
"pins" : [
{
"identity" : "collectionconcurrencykit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
"state" : {
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
"version" : "0.2.0"
}
]
},
"version": 1
},
{
"identity" : "cryptoswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : {
"revision" : "32f641cf24fc7abc1c591a2025e9f2f572648b0f",
"version" : "1.7.2"
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/SourceKitten.git",
"state" : {
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
"version" : "0.34.1"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "4ad606ba5d7673ea60679a61ff867cc1ff8c8e86",
"version" : "1.2.1"
}
},
{
"identity" : "swift-syntax",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "165fc6d22394c1168ff76ab5d951245971ef07e5",
"version" : "509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-05-a"
}
},
{
"identity" : "swiftytexttable",
"kind" : "remoteSourceControl",
"location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
"state" : {
"revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
"version" : "0.9.0"
}
},
{
"identity" : "swxmlhash",
"kind" : "remoteSourceControl",
"location" : "https://github.com/drmohundro/SWXMLHash.git",
"state" : {
"revision" : "4d0f62f561458cbe1f732171e625f03195151b60",
"version" : "7.0.1"
}
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams.git",
"state" : {
"revision" : "f47ba4838c30dbd59998a4e4c87ab620ff959e8a",
"version" : "5.0.5"
}
}
],
"version" : 2
}

View File

@ -1,39 +1,32 @@
// swift-tools-version:5.5
// swift-tools-version:5.7
import PackageDescription
#if os(macOS)
private let addCryptoSwift = false
private let staticSwiftSyntax = true
#else
private let addCryptoSwift = true
private let staticSwiftSyntax = false
#endif
let frameworkDependencies: [Target.Dependency] = [
.product(name: "SourceKittenFramework", package: "SourceKitten"),
.product(name: "SwiftSyntax", package: "SwiftSyntax"),
.product(name: "SwiftSyntaxParser", package: "SwiftSyntax"),
"Yams",
]
+ (addCryptoSwift ? ["CryptoSwift"] : [])
+ (staticSwiftSyntax ? ["lib_InternalSwiftSyntaxParser"] : [])
let package = Package(
name: "SwiftLint",
platforms: [.macOS(.v12)],
products: [
.executable(name: "swiftlint", targets: ["swiftlint"]),
.library(name: "SwiftLintFramework", targets: ["SwiftLintFramework"])
.library(name: "SwiftLintFramework", targets: ["SwiftLintFramework"]),
.plugin(name: "SwiftLintPlugin", targets: ["SwiftLintPlugin"])
],
dependencies: [
.package(name: "swift-argument-parser", url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.1.3")),
.package(name: "SwiftSyntax", url: "https://github.com/apple/swift-syntax.git", .exact("0.50600.1")),
.package(url: "https://github.com/jpsim/SourceKitten.git", from: "0.33.0"),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"),
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.1")),
.package(url: "https://github.com/apple/swift-syntax.git", exact: "509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-05-a"),
.package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.34.1")),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.5"),
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.2.0")
] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.5.1"))] : []),
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.2.0"),
.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.7.2"))
],
targets: [
.plugin(
name: "SwiftLintPlugin",
capability: .buildTool(),
dependencies: [
.target(name: "SwiftLintBinary", condition: .when(platforms: [.macOS])),
.target(name: "swiftlint", condition: .when(platforms: [.linux]))
]
),
.executableTarget(
name: "swiftlint",
dependencies: [
@ -43,26 +36,86 @@ let package = Package(
"SwiftyTextTable",
]
),
.testTarget(
name: "CLITests",
dependencies: [
"swiftlint"
]
),
.target(
name: "SwiftLintCore",
dependencies: [
.product(name: "CryptoSwift", package: "CryptoSwift", condition: .when(platforms: [.linux])),
.target(name: "DyldWarningWorkaround", condition: .when(platforms: [.macOS])),
.product(name: "SourceKittenFramework", package: "SourceKitten"),
.product(name: "SwiftIDEUtils", package: "swift-syntax"),
.product(name: "SwiftOperators", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "SwiftyTextTable", package: "SwiftyTextTable"),
.product(name: "Yams", package: "Yams"),
]
),
.target(
name: "SwiftLintBuiltInRules",
dependencies: ["SwiftLintCore"]
),
.target(
name: "SwiftLintExtraRules",
dependencies: ["SwiftLintCore"]
),
.target(
name: "SwiftLintFramework",
dependencies: frameworkDependencies,
// Pass `-dead_strip_dylibs` to ignore the dynamic version of `lib_InternalSwiftSyntaxParser`
// that ships with SwiftSyntax because we want the static version from
// `StaticInternalSwiftSyntaxParser`.
linkerSettings: staticSwiftSyntax ? [.unsafeFlags(["-Xlinker", "-dead_strip_dylibs"])] : []
dependencies: [
"SwiftLintBuiltInRules",
"SwiftLintCore",
"SwiftLintExtraRules"
]
),
.target(name: "DyldWarningWorkaround"),
.target(
name: "SwiftLintTestHelpers",
dependencies: [
"SwiftLintFramework"
],
path: "Tests/SwiftLintTestHelpers"
),
.testTarget(
name: "SwiftLintFrameworkTests",
dependencies: [
"SwiftLintFramework"
"SwiftLintFramework",
"SwiftLintTestHelpers"
],
exclude: [
"Resources",
]
),
] + (staticSwiftSyntax ? [.binaryTarget(
name: "lib_InternalSwiftSyntaxParser",
url: "https://github.com/keith/StaticInternalSwiftSyntaxParser/releases/download/5.6/lib_InternalSwiftSyntaxParser.xcframework.zip",
checksum: "88d748f76ec45880a8250438bd68e5d6ba716c8042f520998a438db87083ae9d"
)] : [])
.testTarget(
name: "GeneratedTests",
dependencies: [
"SwiftLintFramework",
"SwiftLintTestHelpers"
]
),
.testTarget(
name: "IntegrationTests",
dependencies: [
"SwiftLintFramework",
"SwiftLintTestHelpers"
]
),
.testTarget(
name: "ExtraRulesTests",
dependencies: [
"SwiftLintFramework",
"SwiftLintTestHelpers"
]
),
.binaryTarget(
name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "89651e1c87fb62faf076ef785a5b1af7f43570b2b74c6773526e0d5114e0578e"
)
]
)

View File

@ -0,0 +1,42 @@
import Foundation
import PackagePlugin
#if os(Linux)
import Glibc
#else
import Darwin
#endif
extension Path {
/// Scans the receiver, then all of its parents looking for a configuration file with the name ".swiftlint.yml".
///
/// - returns: Path to the configuration file, or nil if one cannot be found.
func firstConfigurationFileInParentDirectories() -> Path? {
let defaultConfigurationFileName = ".swiftlint.yml"
let proposedDirectory = sequence(
first: self,
next: { path in
guard path.stem.count > 1 else {
// Check we're not at the root of this filesystem, as `removingLastComponent()`
// will continually return the root from itself.
return nil
}
return path.removingLastComponent()
}
).first { path in
let potentialConfigurationFile = path.appending(subpath: defaultConfigurationFileName)
return potentialConfigurationFile.isAccessible()
}
return proposedDirectory?.appending(subpath: defaultConfigurationFileName)
}
/// Safe way to check if the file is accessible from within the current process sandbox.
private func isAccessible() -> Bool {
let result = string.withCString { pointer in
access(pointer, R_OK)
}
return result == 0
}
}

View File

@ -0,0 +1,76 @@
import Foundation
import PackagePlugin
@main
struct SwiftLintPlugin: BuildToolPlugin {
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
guard let sourceTarget = target as? SourceModuleTarget else {
return []
}
return createBuildCommands(
inputFiles: sourceTarget.sourceFiles(withSuffix: "swift").map(\.path),
packageDirectory: context.package.directory,
workingDirectory: context.pluginWorkDirectory,
tool: try context.tool(named: "swiftlint")
)
}
private func createBuildCommands(
inputFiles: [Path],
packageDirectory: Path,
workingDirectory: Path,
tool: PluginContext.Tool
) -> [Command] {
if inputFiles.isEmpty {
// Don't lint anything if there are no Swift source files in this target
return []
}
var arguments = [
"lint",
"--quiet",
// We always pass all of the Swift source files in the target to the tool,
// so we need to ensure that any exclusion rules in the configuration are
// respected.
"--force-exclude",
"--cache-path", "\(workingDirectory)"
]
// Manually look for configuration files, to avoid issues when the plugin does not execute our tool from the
// package source directory.
if let configuration = packageDirectory.firstConfigurationFileInParentDirectories() {
arguments.append(contentsOf: ["--config", "\(configuration.string)"])
}
arguments += inputFiles.map(\.string)
// We are not producing output files and this is needed only to not include cache files into bundle
let outputFilesDirectory = workingDirectory.appending("Output")
return [
.prebuildCommand(
displayName: "SwiftLint",
executable: tool.path,
arguments: arguments,
outputFilesDirectory: outputFilesDirectory
)
]
}
}
#if canImport(XcodeProjectPlugin)
import XcodeProjectPlugin
extension SwiftLintPlugin: XcodeBuildToolPlugin {
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
let inputFilePaths = target.inputFiles
.filter { $0.type == .source && $0.path.extension == "swift" }
.map(\.path)
return createBuildCommands(
inputFiles: inputFilePaths,
packageDirectory: context.xcodeProject.directory,
workingDirectory: context.pluginWorkDirectory,
tool: try context.tool(named: "swiftlint")
)
}
}
#endif

182
README.md
View File

@ -1,14 +1,14 @@
# SwiftLint
A tool to enforce Swift style and conventions, loosely based on the now archived [GitHub Swift Style Guide](https://github.com/github/swift-style-guide). SwiftLint enforces the style guide rules that are generally accepted by the Swift community. These rules are well described in popular style guides like [Ray Wenderlich's Swift Style Guide](https://github.com/raywenderlich/swift-style-guide).
A tool to enforce Swift style and conventions, loosely based on the now archived [GitHub Swift Style Guide](https://github.com/github/swift-style-guide). SwiftLint enforces the style guide rules that are generally accepted by the Swift community. These rules are well described in popular style guides like [Kodeco's Swift Style Guide](https://github.com/kodecocodes/swift-style-guide).
SwiftLint hooks into [Clang](http://clang.llvm.org) and
[SourceKit](http://www.jpsim.com/uncovering-sourcekit) to use the
[AST](http://clang.llvm.org/docs/IntroductionToTheClangAST.html) representation
of your source files for more accurate results.
[![Build Status](https://dev.azure.com/jpsim/SwiftLint/_apis/build/status/realm.SwiftLint?branchName=master)](https://dev.azure.com/jpsim/SwiftLint/_build/latest?definitionId=4?branchName=master)
[![codecov.io](https://codecov.io/github/realm/SwiftLint/coverage.svg?branch=master)](https://codecov.io/github/realm/SwiftLint?branch=master)
[![Build Status](https://dev.azure.com/jpsim/SwiftLint/_apis/build/status/realm.SwiftLint?branchName=main)](https://dev.azure.com/jpsim/SwiftLint/_build/latest?definitionId=4?branchName=main)
[![codecov.io](https://codecov.io/github/realm/SwiftLint/coverage.svg?branch=main)](https://codecov.io/github/realm/SwiftLint?branch=main)
![](assets/screenshot.png)
@ -16,7 +16,7 @@ This project adheres to the [Contributor Covenant Code of Conduct](https://realm
By participating, you are expected to uphold this code. Please report
unacceptable behavior to [info@realm.io](mailto:info@realm.io).
> Language Switch: [中文](https://github.com/realm/SwiftLint/blob/master/README_CN.md), [한국어](https://github.com/realm/SwiftLint/blob/master/README_KR.md).
> Language Switch: [中文](https://github.com/realm/SwiftLint/blob/main/README_CN.md), [한국어](https://github.com/realm/SwiftLint/blob/main/README_KR.md).
## Installation
@ -64,19 +64,25 @@ You can also build and install from source by cloning this project and running
### Using Bazel
Put this in your `WORKSPACE`:
Put this in your `MODULE.bazel`:
```bzl
bazel_dep(name = "swiftlint", version = "0.50.4", repo_name = "SwiftLint")
```
Or put this in your `WORKSPACE`:
<details>
<summary>WORKSPACE</summary>
```python
```bzl
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "build_bazel_rules_apple",
sha256 = "36072d4f3614d309d6a703da0dfe48684ec4c65a89611aeb9590b45af7a3e592",
url = "https://github.com/bazelbuild/rules_apple/releases/download/1.0.1/rules_apple.1.0.1.tar.gz",
sha256 = "f94e6dddf74739ef5cb30f000e13a2a613f6ebfa5e63588305a71fce8a8a9911",
url = "https://github.com/bazelbuild/rules_apple/releases/download/1.1.3/rules_apple.1.1.3.tar.gz",
)
load(
@ -102,8 +108,8 @@ swift_rules_extra_dependencies()
http_archive(
name = "SwiftLint",
sha256 = "e954f4483f7f4cf523896693ee3505585f6beb0f791e362b42d9bdbb615f051a",
url = "https://github.com/realm/SwiftLint/releases/download/0.49.0-rc.1/bazel.tar.gz",
sha256 = "7c454ff4abeeecdd9513f6293238a6d9f803b587eb93de147f9aa1be0d8337c4",
url = "https://github.com/realm/SwiftLint/releases/download/0.49.1/bazel.tar.gz",
)
load("@SwiftLint//bazel:repos.bzl", "swiftlint_repos")
@ -130,7 +136,7 @@ bazel run -c opt @SwiftLint//:swiftlint
To get a high-level overview of recommended ways to integrate SwiftLint into your project,
we encourage you to watch this presentation or read the transcript:
[![Presentation](assets/presentation.svg)](https://academy.realm.io/posts/slug-jp-simard-swiftlint/)
[![Presentation](assets/presentation.svg)](https://youtu.be/9Z1nTMTejqU)
### Xcode
@ -152,7 +158,10 @@ folder by default. To instruct Xcode where to find SwiftLint, you can either add
`/opt/homebrew/bin` to the `PATH` environment variable in your build phase
```bash
export PATH="$PATH:/opt/homebrew/bin"
if [[ "$(uname -m)" == arm64 ]]; then
export PATH="/opt/homebrew/bin:$PATH"
fi
if which swiftlint > /dev/null; then
swiftlint
else
@ -183,19 +192,52 @@ If you've installed SwiftLint via CocoaPods the script should look like this:
"${PODS_ROOT}/SwiftLint/swiftlint"
```
### AppCode
### Plug-in Support
To integrate SwiftLint with AppCode, install
[this plugin](https://plugins.jetbrains.com/plugin/9175) and configure
SwiftLint's installed path in the plugin's preferences.
The `fix` action is available via `⌥⏎`.
SwiftLint can be used as a build tool plug-in for both Xcode projects as well as
Swift packages.
### Atom
> Due to limitations with Swift Package Manager Plug-ins this is only
recommended for projects that have a SwiftLint configuration in their root directory as
there is currently no way to pass any additional options to the SwiftLint executable.
To integrate SwiftLint with [Atom](https://atom.io/), install the
[`linter-swiftlint`](https://atom.io/packages/linter-swiftlint) package from
APM.
#### Xcode
You can integrate SwiftLint as an Xcode Build Tool Plug-in if you're working
with a project in Xcode.
Add SwiftLint as a package dependency to your project without linking any of the
products.
Select the target you want to add linting to and open the `Build Phases` inspector.
Open `Run Build Tool Plug-ins` and select the `+` button.
Select `SwiftLintPlugin` from the list and add it to the project.
![](assets/select-swiftlint-plugin.png)
For unattended use (e.g. on CI), you can disable the package validation dialog by
* individually passing `-skipPackagePluginValidation` to `xcodebuild` or
* globally setting `defaults write com.apple.dt.Xcode IDESkipPackagePluginFingerprintValidatation -bool YES`
for that user.
_Note: This implicitly trusts all Xcode package plugins and bypasses Xcode's package validation
dialogs, which has security implications._
#### Swift Package
You can integrate SwiftLint as a Swift Package Manager Plug-in if you're working with
a Swift Package with a `Package.swift` manifest.
Add SwiftLint as a package dependency to your `Package.swift` file.
Add SwiftLint to a target using the `plugins` parameter.
```swift
.target(
...
plugins: [.plugin(name: "SwiftLintPlugin", package: "SwiftLint")]
),
```
### Visual Studio Code
@ -266,6 +308,7 @@ SUBCOMMANDS:
docs Open SwiftLint documentation website in the default web browser
generate-docs Generates markdown documentation for all rules
lint (default) Print lint warnings and errors
reporters Display the list of reporters and their identifiers
rules Display the list of rules and their identifiers
version Display the current version of SwiftLint
@ -333,12 +376,21 @@ Once [installed](https://pre-commit.com/#install), add this to the
```yaml
repos:
- repo: https://github.com/realm/SwiftLint
rev: 0.44.0
rev: 0.50.3
hooks:
- id: swiftlint
```
Adjust `rev` to the SwiftLint version of your choice.
Adjust `rev` to the SwiftLint version of your choice. `pre-commit autoupdate` can be used to update to the current version.
SwiftLint can be configured using `entry` to apply fixes and fail on errors:
```yaml
- repo: https://github.com/realm/SwiftLint
rev: 0.50.3
hooks:
- id: swiftlint
entry: swiftlint --fix --strict
```
## Rules
@ -349,7 +401,7 @@ continues to contribute more over time.
You can find an updated list of rules and more information about them
[here](https://realm.github.io/SwiftLint/rule-directory.html).
You can also check [Source/SwiftLintFramework/Rules](https://github.com/realm/SwiftLint/tree/master/Source/SwiftLintFramework/Rules)
You can also check [Source/SwiftLintBuiltInRules/Rules](https://github.com/realm/SwiftLint/tree/main/Source/SwiftLintBuiltInRules/Rules)
directory to see their implementation.
### Opt-In Rules
@ -427,7 +479,9 @@ run SwiftLint from. The following parameters can be configured:
Rule inclusion:
* `disabled_rules`: Disable rules from the default enabled set.
* `opt_in_rules`: Enable rules that are not part of the default set.
* `opt_in_rules`: Enable rules that are not part of the default set. The
special `all` identifier will enable all opt in linter rules, except the ones
listed in `disabled_rules`.
* `only_rules`: Only the rules specified in this list will be enabled.
Cannot be specified alongside `disabled_rules` or `opt_in_rules`.
* `analyzer_rules`: This is an entirely separate list of rules that are only
@ -449,6 +503,9 @@ opt_in_rules: # some rules are turned off by default, so you need to opt-in
# - empty_parameters
# - vertical_whitespace
analyzer_rules: # Rules run by `swiftlint analyze`
- explicit_self
included: # paths to include during linting. `--path` is ignored if present.
- Source
excluded: # paths to ignore during linting. Takes precedence over `included`.
@ -457,8 +514,9 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
- Source/ExcludedFolder
- Source/ExcludedFile.swift
- Source/*/ExcludedFile.swift # Exclude files with a wildcard
analyzer_rules: # Rules run by `swiftlint analyze`
- explicit_self
# If true, SwiftLint will not fail if no lintable files are found.
allow_zero_lintable_files: false
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
@ -492,13 +550,29 @@ identifier_name:
- id
- URL
- GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging)
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging, summary)
```
You can also use environment variables in your configuration file,
by using `${SOME_VARIABLE}` in a string.
#### Defining Custom Rules
### Defining Custom Rules
In addition to the rules that the main SwiftLint project ships with, SwiftLint
can also run two types of custom rules that you can define yourself in your own
projects:
#### 1. Swift Custom Rules
These rules are written the same way as the Swift-based rules that ship with
SwiftLint so they're fast, accurate, can leverage SwiftSyntax, can be unit
tested, and more.
Using these requires building SwiftLint with Bazel as described in
[this video](https://vimeo.com/820572803) or its associated code in
[github.com/jpsim/swiftlint-bazel-example](https://github.com/jpsim/swiftlint-bazel-example).
#### 2. Regex Custom Rules
You can define custom regex-based rules in your configuration file using the
following syntax:
@ -531,29 +605,41 @@ You can filter the matches by providing one or more `match_kinds`, which will
reject matches that include syntax kinds that are not present in this list. Here
are all the possible syntax kinds:
* argument
* attribute.builtin
* attribute.id
* buildconfig.id
* buildconfig.keyword
* comment
* comment.mark
* comment.url
* doccomment
* doccomment.field
* identifier
* keyword
* number
* objectliteral
* parameter
* placeholder
* string
* string_interpolation_anchor
* typeidentifier
* `argument`
* `attribute.builtin`
* `attribute.id`
* `buildconfig.id`
* `buildconfig.keyword`
* `comment`
* `comment.mark`
* `comment.url`
* `doccomment`
* `doccomment.field`
* `identifier`
* `keyword`
* `number`
* `objectliteral`
* `parameter`
* `placeholder`
* `string`
* `string_interpolation_anchor`
* `typeidentifier`
All syntax kinds used in a snippet of Swift code can be extracted asking
[SourceKitten](https://github.com/jpsim/SourceKitten). For example,
`sourcekitten syntax --text "struct S {}"` delivers
* `source.lang.swift.syntaxtype.keyword` for the `struct` keyword and
* `source.lang.swift.syntaxtype.identifier` for its name `S`
which match to `keyword` and `identifier` in the above list.
If using custom rules in combination with `only_rules`, make sure to add
`custom_rules` as an item under `only_rules`.
Unlike Swift custom rules, you can use official SwiftLint builds
(e.g. from Homebrew) to run regex custom rules.
### Auto-correct
SwiftLint can automatically correct certain violations. Files on disk are

View File

@ -1,11 +1,11 @@
# SwiftLint
SwiftLint 是一个用于强制检查 Swift 代码风格和规定的一个工具,基本上以 [Ray Wenderlich's Swift 代码风格指南](https://github.com/raywenderlich/swift-style-guide)为基础。
SwiftLint 是一个用于强制检查 Swift 代码风格和规定的一个工具,基本上以 [Kodeco's Swift 代码风格指南](https://github.com/kodecocodes/swift-style-guide)为基础。
SwiftLint Hook 了 [Clang](http://clang.llvm.org) 和 [SourceKit](http://www.jpsim.com/uncovering-sourcekit) 从而能够使用 [AST](http://clang.llvm.org/docs/IntroductionToTheClangAST.html) 来表示源代码文件的更多精确结果。
[![Build Status](https://dev.azure.com/jpsim/SwiftLint/_apis/build/status/realm.SwiftLint?branchName=master)](https://dev.azure.com/jpsim/SwiftLint/_build/latest?definitionId=4?branchName=master)
[![codecov.io](https://codecov.io/github/realm/SwiftLint/coverage.svg?branch=master)](https://codecov.io/github/realm/SwiftLint?branch=master)
[![Build Status](https://dev.azure.com/jpsim/SwiftLint/_apis/build/status/realm.SwiftLint?branchName=main)](https://dev.azure.com/jpsim/SwiftLint/_build/latest?definitionId=4?branchName=main)
[![codecov.io](https://codecov.io/github/realm/SwiftLint/coverage.svg?branch=main)](https://codecov.io/github/realm/SwiftLint?branch=main)
![](assets/screenshot.png)
@ -167,7 +167,7 @@ SwiftLint 已经包含了超过 75 条规则,并且我们希望 Swift 社区
你可以在 [Rule Directory](https://realm.github.io/SwiftLint/rule-directory.html) 找到规则的更新列表和更多信息。
你也可以检视 [Source/SwiftLintFramework/Rules](Source/SwiftLintFramework/Rules) 目录来查看它们的实现。
你也可以检视 [Source/SwiftLintBuiltInRules/Rules](Source/SwiftLintBuiltInRules/Rules) 目录来查看它们的实现。
`opt_in_rules` 默认是关闭的(即,你需要在你的配置文件中明确地打开它们)。

View File

@ -1,11 +1,11 @@
# SwiftLint
SwiftLint는 스위프트 스타일 및 컨벤션을 강제하기 위한 도구로, [Ray Wenderlich 스위프트 스타일 가이드](https://github.com/raywenderlich/swift-style-guide)에 대략적인 기반을 두고 있습니다.
SwiftLint는 스위프트 스타일 및 컨벤션을 강제하기 위한 도구로, [Kodeco 스위프트 스타일 가이드](https://github.com/kodecocodes/swift-style-guide)에 대략적인 기반을 두고 있습니다.
SwiftLint는 좀 더 정확한 결과를 위해 [Clang](http://clang.llvm.org)과 [SourceKit](http://www.jpsim.com/uncovering-sourcekit)에 연결하여 소스 파일의 [AST](http://clang.llvm.org/docs/IntroductionToTheClangAST.html) 표현을 사용합니다.
[![Build Status](https://dev.azure.com/jpsim/SwiftLint/_apis/build/status/realm.SwiftLint?branchName=master)](https://dev.azure.com/jpsim/SwiftLint/_build/latest?definitionId=4?branchName=master)
[![codecov.io](https://codecov.io/github/realm/SwiftLint/coverage.svg?branch=master)](https://codecov.io/github/realm/SwiftLint?branch=master)
[![Build Status](https://dev.azure.com/jpsim/SwiftLint/_apis/build/status/realm.SwiftLint?branchName=main)](https://dev.azure.com/jpsim/SwiftLint/_build/latest?definitionId=4?branchName=main)
[![codecov.io](https://codecov.io/github/realm/SwiftLint/coverage.svg?branch=main)](https://codecov.io/github/realm/SwiftLint?branch=main)
![](assets/screenshot.png)
@ -75,7 +75,10 @@ fi
그 이유는, 애플 실리콘 기반 맥에서 Homebrew는 기본적으로 바이너리들을 `/opt/homebrew/bin`에 저장하기 때문입니다. SwiftLint가 어디 있는지 찾는 것을 Xcode에 알려주기 위해, build phase에서 `/opt/homebrew/bin``PATH` 환경 변수에 동시에 추가하여야 합니다.
```bash
export PATH="$PATH:/opt/homebrew/bin"
if [[ "$(uname -m)" == arm64 ]]; then
export PATH="/opt/homebrew/bin:$PATH"
fi
if which swiftlint > /dev/null; then
swiftlint
else
@ -180,9 +183,9 @@ $ TOOLCHAINS=com.apple.dt.toolchain.Swift_2_3 swiftlint autocorrect
## 룰
SwiftLint에는 75개가 넘는 룰들이 있고, 스위프트 커뮤니티(바로 여러분들!)는 이를 지속적으로 발전시켜 가고 있습니다. [풀 리퀘스트](CONTRIBUTING.md)는 언제나 환영입니다.
SwiftLint에는 200개가 넘는 룰들이 있고, 스위프트 커뮤니티(바로 여러분들!)는 이를 지속적으로 발전시켜 가고 있습니다. [풀 리퀘스트](CONTRIBUTING.md)는 언제나 환영입니다.
현재 구현된 룰 전체를 확인하려면 [Source/SwiftLintFramework/Rules](Source/SwiftLintFramework/Rules)를 살펴보세요.
현재 구현된 룰 전체를 확인하려면 [Source/SwiftLintBuiltInRules/Rules](Source/SwiftLintBuiltInRules/Rules)를 살펴보세요.
`opt_in_rules`는 기본적으로 비활성화되어 있습니다. (즉, 설정 파일에서 명시적으로 해당 룰을 활성화해야 합니다.)

View File

@ -7,19 +7,8 @@ For SwiftLint contributors, follow these steps to cut a release:
* FabricSoftenerRule
* Top Loading
* Fresh Out Of The Dryer
1. Push new version: `make push_version "0.2.0: Tumble Dry"`
1. Make sure you have the latest stable Xcode version installed and
`xcode-select`ed.
1. Create the pkg installer, framework zip, portable zip,
macos artifactbundle zip, and Linux zip:
`make release`
1. Create a GitHub release: https://github.com/realm/SwiftLint/releases/new
* Specify the tag you just pushed from the dropdown.
* Set the release title to the new version number & release name.
* Add the changelog section to the release description text box.
* Upload the pkg installer, framework zip, portable zip,
macos artifactbundle zip, and Linux zip you just built
to the GitHub release binaries.
* Click "Publish release".
1. Publish to Homebrew and CocoaPods trunk: `make publish`
`xcode-select`ed
1. Release new version: `make release "0.2.0: Tumble Dry"`
1. Wait for the Docker CI job to finish then run: `make zip_linux_release`
1. Celebrate. :tada:

View File

@ -0,0 +1,15 @@
#ifdef __APPLE__
#include "objc_dupclass.h"
OBJC_DUPCLASS(_TtC11SwiftSyntax11SyntaxArena);
OBJC_DUPCLASS(_TtC11SwiftSyntax13SyntaxVisitor);
OBJC_DUPCLASS(_TtC11SwiftSyntax14SyntaxRewriter);
OBJC_DUPCLASS(_TtC11SwiftSyntax16BumpPtrAllocator);
OBJC_DUPCLASS(_TtC11SwiftSyntax16SyntaxAnyVisitor);
OBJC_DUPCLASS(_TtC11SwiftSyntax18ParsingSyntaxArena);
OBJC_DUPCLASS(_TtC11SwiftSyntax23SourceLocationConverter);
OBJC_DUPCLASS(_TtC11SwiftSyntax26IncrementalParseTransition);
OBJC_DUPCLASS(_TtC11SwiftSyntax35IncrementalParseReusedNodeCollector);
#endif // __APPLE__

View File

@ -0,0 +1,19 @@
// https://github.com/keith/objc_dupclass
#include <stdint.h>
// TODO: This isn't entirely accurate, but I'm not sure how to more accurately determine
#if (defined(__arm64__) || defined(DUPCLASS_FORCE_DATA_CONST)) && !defined(DUPCLASS_FORCE_DATA)
#define SECTION "__DATA_CONST"
#else
#define SECTION "__DATA"
#endif
// Struct layout from https://github.com/apple-oss-distributions/objc4/blob/8701d5672d3fd3cd817aeb84db1077aafe1a1604/runtime/objc-abi.h#L175-L183
#define OBJC_DUPCLASS(kclass) \
__attribute__((used)) __attribute__((visibility("hidden"))) \
static struct { uint32_t version; uint32_t flags; const char name[64]; } \
const __duplicate_class_##kclass = { 0, 0, #kclass }; \
\
__attribute__((used)) __attribute__((visibility("hidden"))) \
__attribute__((section (SECTION",__objc_dupclass"))) \
const void* __set___objc_dupclass_sym___duplicate_class_##kclass = &__duplicate_class_##kclass

View File

@ -0,0 +1 @@
@_exported import SwiftLintCore

View File

@ -0,0 +1,187 @@
import SourceKittenFramework
/// Struct to represent SwiftUI ViewModifiers for the purpose of finding modifiers in a substructure.
struct SwiftUIModifier {
/// Name of the modifier.
let name: String
/// List of arguments to check for in the modifier.
let arguments: [Argument]
struct Argument {
/// Name of the argument we want to find. For single unnamed arguments, use the empty string.
let name: String
/// Whether or not the argument is required. If the argument is present, value checks are enforced.
/// Allows for better handling of modifiers with default values for certain arguments where we want
/// to ensure that the default value is used.
let required: Bool
/// List of possible values for the argument. Typically should just be a list with a single element,
/// but allows for the flexibility of checking for multiple possible values. To only check for the presence
/// of the modifier and not enforce any certain values, pass an empty array. All values are parsed as
/// Strings; for other types (boolean, numeric, optional, etc) types you can check for "true", "5", "nil", etc.
let values: [String]
/// Success criteria used for matching values (prefix, suffix, substring, exact match, or none).
let matchType: MatchType
init(name: String, required: Bool = true, values: [String], matchType: MatchType = .exactMatch) {
self.name = name
self.required = required
self.values = values
self.matchType = matchType
}
}
enum MatchType {
case prefix, suffix, substring, exactMatch
/// Compares the parsed argument value to a target value for the given match type
/// and returns true is a match is found.
func matches(argumentValue: String, targetValue: String) -> Bool {
switch self {
case .prefix:
return argumentValue.hasPrefix(targetValue)
case .suffix:
return argumentValue.hasSuffix(targetValue)
case .substring:
return argumentValue.contains(targetValue)
case .exactMatch:
return argumentValue == targetValue
}
}
}
}
/// Extensions for recursively checking SwiftUI code for certain modifiers.
extension SourceKittenDictionary {
/// Call on a SwiftUI View to recursively check the substructure for a certain modifier with certain arguments.
/// - Parameters:
/// - modifiers: A list of `SwiftUIModifier` structs to check for in the view's substructure.
/// In most cases, this can just be a single modifier, but since some modifiers have
/// multiple versions, this enables checking for any modifier from the list.
/// - file: The SwiftLintFile object for the current file, used to extract argument values.
/// - Returns: A boolean value representing whether or not the given modifier with the specified
/// arguments appears in the view's substructure.
func hasModifier(anyOf modifiers: [SwiftUIModifier], in file: SwiftLintFile) -> Bool {
// SwiftUI ViewModifiers are treated as `call` expressions, and we make sure we can get the expression's name.
guard expressionKind == .call, let name else {
return false
}
// If any modifier from the list matches, return true.
for modifier in modifiers {
// Check for the given modifier name
guard name.hasSuffix(modifier.name) else {
continue
}
// Check arguments.
var matchesArgs = true
for argument in modifier.arguments {
var foundArg = false
var argValue: String?
// Check for single unnamed argument.
if argument.name.isEmpty {
foundArg = true
argValue = getSingleUnnamedArgumentValue(in: file)
} else if let parsedArgument = enclosedArguments.first(where: { $0.name == argument.name }) {
foundArg = true
argValue = parsedArgument.getArgumentValue(in: file)
}
// If argument is not required and we didn't find it, continue.
if !foundArg && !argument.required {
continue
}
// Otherwise, we must have found an argument with a non-nil value to continue.
guard foundArg, let argumentValue = argValue else {
matchesArgs = false
break
}
// Argument value can match any of the options given in the argument struct.
if argument.values.isEmpty || argument.values.contains(where: {
argument.matchType.matches(argumentValue: argumentValue, targetValue: $0)
}) {
// Found a match, continue to next argument.
continue
} else {
// Did not find a match, exit loop over arguments.
matchesArgs = false
break
}
}
// Return true if all arguments matched
if matchesArgs {
return true
}
}
// Recursively check substructure.
// SwiftUI literal Views with modifiers will have a SourceKittenDictionary structure like:
// Image("myImage").resizable().accessibility(hidden: true).frame
// --> Image("myImage").resizable().accessibility
// --> Image("myImage").resizable
// --> Image
return substructure.contains(where: { $0.hasModifier(anyOf: modifiers, in: file) })
}
// MARK: Sample use cases of `hasModifier` that are used in multiple rules
/// Whether or not the dictionary represents a SwiftUI View with an `accesibilityHidden(true)`
/// or `accessibility(hidden: true)` modifier.
func hasAccessibilityHiddenModifier(in file: SwiftLintFile) -> Bool {
return hasModifier(
anyOf: [
SwiftUIModifier(
name: "accessibilityHidden",
arguments: [.init(name: "", values: ["true"])]
),
SwiftUIModifier(
name: "accessibility",
arguments: [.init(name: "hidden", values: ["true"])]
)
],
in: file
)
}
/// Whether or not the dictionary represents a SwiftUI View with an `accessibilityElement()` or
/// `accessibilityElement(children: .ignore)` modifier (`.ignore` is the default parameter value).
func hasAccessibilityElementChildrenIgnoreModifier(in file: SwiftLintFile) -> Bool {
return hasModifier(
anyOf: [
SwiftUIModifier(
name: "accessibilityElement",
arguments: [.init(name: "children", required: false, values: [".ignore"], matchType: .suffix)]
)
],
in: file
)
}
// MARK: Helpers to extract argument values
/// Helper to get the value of an argument.
func getArgumentValue(in file: SwiftLintFile) -> String? {
guard expressionKind == .argument, let bodyByteRange else {
return nil
}
return file.stringView.substringWithByteRange(bodyByteRange)
}
/// Helper to get the value of a single unnamed argument to a function call.
func getSingleUnnamedArgumentValue(in file: SwiftLintFile) -> String? {
guard expressionKind == .call, let bodyByteRange else {
return nil
}
return file.stringView.substringWithByteRange(bodyByteRange)
}
}

View File

@ -0,0 +1,109 @@
import SwiftSyntax
import SwiftSyntaxBuilder
/// A helper to hold a visitor and rewriter that can lint and correct legacy NS/CG functions to a more modern syntax.
enum LegacyFunctionRuleHelper {
final class Visitor: ViolationsSyntaxVisitor {
private let legacyFunctions: [String: RewriteStrategy]
init(legacyFunctions: [String: RewriteStrategy]) {
self.legacyFunctions = legacyFunctions
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: FunctionCallExprSyntax) {
if node.isLegacyFunctionExpression(legacyFunctions: legacyFunctions) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
enum RewriteStrategy {
case equal
case property(name: String)
case function(name: String, argumentLabels: [String], reversed: Bool = false)
var expectedInitialArguments: Int {
switch self {
case .equal:
return 2
case .property:
return 1
case .function(name: _, argumentLabels: let argumentLabels, reversed: _):
return argumentLabels.count + 1
}
}
}
final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
private let locationConverter: SourceLocationConverter
private let disabledRegions: [SourceRange]
private let legacyFunctions: [String: RewriteStrategy]
init(
legacyFunctions: [String: RewriteStrategy],
locationConverter: SourceLocationConverter,
disabledRegions: [SourceRange]
) {
self.legacyFunctions = legacyFunctions
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
guard
node.isLegacyFunctionExpression(legacyFunctions: legacyFunctions),
let funcName = node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
else {
return super.visit(node)
}
correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let trimmedArguments = node.argumentList.map { $0.trimmingTrailingComma() }
let rewriteStrategy = legacyFunctions[funcName]
let expr: ExprSyntax
switch rewriteStrategy {
case .equal:
expr = "\(trimmedArguments[0]) == \(trimmedArguments[1])"
case let .property(name: propertyName):
expr = "\(trimmedArguments[0]).\(raw: propertyName)"
case let .function(name: functionName, argumentLabels: argumentLabels, reversed: reversed):
let arguments = reversed ? trimmedArguments.reversed() : trimmedArguments
let params = zip(argumentLabels, arguments.dropFirst())
.map { $0.isEmpty ? "\($1)" : "\($0): \($1)" }
.joined(separator: ", ")
expr = "\(arguments[0]).\(raw: functionName)(\(raw: params))"
case .none:
return super.visit(node)
}
return expr
.with(\.leadingTrivia, node.leadingTrivia)
.with(\.trailingTrivia, node.trailingTrivia)
}
}
}
private extension FunctionCallExprSyntax {
func isLegacyFunctionExpression(legacyFunctions: [String: LegacyFunctionRuleHelper.RewriteStrategy]) -> Bool {
guard
let calledExpression = calledExpression.as(IdentifierExprSyntax.self),
let rewriteStrategy = legacyFunctions[calledExpression.identifier.text],
argumentList.count == rewriteStrategy.expectedInitialArguments
else {
return false
}
return true
}
}
private extension TupleExprElementSyntax {
func trimmingTrailingComma() -> TupleExprElementSyntax {
self.trimmed.with(\.trailingComma, nil).trimmed
}
}

View File

@ -1,13 +1,16 @@
// Generated using Sourcery 1.8.2 https://github.com/krzysztofzablocki/Sourcery
// Generated using Sourcery 2.0.2 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
/// The rule list containing all available rules built into SwiftLint.
public let primaryRuleList = RuleList(rules: [
public let builtInRules: [Rule.Type] = [
AccessibilityLabelForImageRule.self,
AccessibilityTraitForButtonRule.self,
AnonymousArgumentInMultilineClosureRule.self,
AnyObjectProtocolRule.self,
ArrayInitRule.self,
AttributesRule.self,
BalancedXCTestLifecycleRule.self,
BlanketDisableCommandRule.self,
BlockBasedKVORule.self,
CaptureVariableRule.self,
ClassDelegateProtocolRule.self,
@ -30,9 +33,9 @@ public let primaryRuleList = RuleList(rules: [
ContainsOverRangeNilComparisonRule.self,
ControlStatementRule.self,
ConvenienceTypeRule.self,
CustomRules.self,
CyclomaticComplexityRule.self,
DeploymentTargetRule.self,
DirectReturnRule.self,
DiscardedNotificationCenterObserverRule.self,
DiscouragedAssertRule.self,
DiscouragedDirectInitRule.self,
@ -40,6 +43,7 @@ public let primaryRuleList = RuleList(rules: [
DiscouragedObjectLiteralRule.self,
DiscouragedOptionalBooleanRule.self,
DiscouragedOptionalCollectionRule.self,
DuplicateConditionsRule.self,
DuplicateEnumCasesRule.self,
DuplicateImportsRule.self,
DuplicatedKeyInDictionaryLiteralRule.self,
@ -86,6 +90,7 @@ public let primaryRuleList = RuleList(rules: [
InclusiveLanguageRule.self,
IndentationWidthRule.self,
InertDeferRule.self,
InvalidSwiftLintCommandRule.self,
IsDisjointRule.self,
JoinedDefaultParameterRule.self,
LargeTupleRule.self,
@ -101,7 +106,8 @@ public let primaryRuleList = RuleList(rules: [
LegacyRandomRule.self,
LetVarWhitespaceRule.self,
LineLengthRule.self,
LiteralExpressionEndIdentationRule.self,
LiteralExpressionEndIndentationRule.self,
LocalDocCommentRule.self,
LowerACLThanParentRule.self,
MarkRule.self,
MissingDocsRule.self,
@ -115,12 +121,14 @@ public let primaryRuleList = RuleList(rules: [
MultipleClosuresWithTrailingClosureRule.self,
NSLocalizedStringKeyRule.self,
NSLocalizedStringRequireBundleRule.self,
NSNumberInitAsFunctionReferenceRule.self,
NSObjectPreferIsEqualRule.self,
NestingRule.self,
NimbleOperatorRule.self,
NoExtensionAccessModifierRule.self,
NoFallthroughOnlyRule.self,
NoGroupingExtensionRule.self,
NoMagicNumbersRule.self,
NoSpaceInMethodCallRule.self,
NotificationCenterDetachmentRule.self,
NumberSeparatorRule.self,
@ -133,6 +141,7 @@ public let primaryRuleList = RuleList(rules: [
OverriddenSuperCallRule.self,
OverrideInExtensionRule.self,
PatternMatchingKeywordsRule.self,
PeriodSpacingRule.self,
PreferNimbleRule.self,
PreferSelfInStaticReferencesRule.self,
PreferSelfTypeOverTypeOfSelfRule.self,
@ -156,6 +165,7 @@ public let primaryRuleList = RuleList(rules: [
RedundantNilCoalescingRule.self,
RedundantObjcAttributeRule.self,
RedundantOptionalInitializationRule.self,
RedundantSelfInClosureRule.self,
RedundantSetAccessControlRule.self,
RedundantStringEnumValueRule.self,
RedundantTypeAnnotationRule.self,
@ -164,16 +174,19 @@ public let primaryRuleList = RuleList(rules: [
RequiredEnumCaseRule.self,
ReturnArrowWhitespaceRule.self,
ReturnValueFromVoidFunctionRule.self,
SelfBindingRule.self,
SelfInPropertyInitializationRule.self,
ShorthandOperatorRule.self,
ShorthandOptionalBindingRule.self,
SingleTestClassRule.self,
SortedEnumCasesRule.self,
SortedFirstLastRule.self,
SortedImportsRule.self,
StatementPositionRule.self,
StaticOperatorRule.self,
StrictFilePrivateRule.self,
StrongIBOutletRule.self,
SuperfluousDisableCommandRule.self,
SuperfluousElseRule.self,
SwitchCaseAlignmentRule.self,
SwitchCaseOnNewlineRule.self,
SyntacticSugarRule.self,
@ -191,6 +204,7 @@ public let primaryRuleList = RuleList(rules: [
TypesafeArrayInitRule.self,
UnavailableConditionRule.self,
UnavailableFunctionRule.self,
UnhandledThrowingTaskRule.self,
UnneededBreakInSwitchRule.self,
UnneededParenthesesInClosureArgumentRule.self,
UnownedVariableCaptureRule.self,
@ -216,4 +230,4 @@ public let primaryRuleList = RuleList(rules: [
XCTFailMessageRule.self,
XCTSpecificMatcherRule.self,
YodaConditionRule.self
] + extraRules())
]

View File

@ -23,7 +23,7 @@ enum ImportUsage {
case .unused:
return nil
case .missing(let module):
return "Missing import for referenced module '\(module)'."
return "Missing import for referenced module '\(module)'"
}
}
}

View File

@ -0,0 +1,60 @@
import SwiftSyntax
struct AnonymousArgumentInMultilineClosureRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "anonymous_argument_in_multiline_closure",
name: "Anonymous Argument in Multiline Closure",
description: "Use named arguments in multiline closures",
kind: .idiomatic,
nonTriggeringExamples: [
Example("closure { $0 }"),
Example("closure { print($0) }"),
Example("""
closure { arg in
print(arg)
}
"""),
Example("""
closure { arg in
nestedClosure { $0 + arg }
}
""")
],
triggeringExamples: [
Example("""
closure {
print($0)
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(locationConverter: file.locationConverter)
}
}
private extension AnonymousArgumentInMultilineClosureRule {
final class Visitor: ViolationsSyntaxVisitor {
private let locationConverter: SourceLocationConverter
init(locationConverter: SourceLocationConverter) {
self.locationConverter = locationConverter
super.init(viewMode: .sourceAccurate)
}
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
let startLocation = locationConverter.location(for: node.leftBrace.positionAfterSkippingLeadingTrivia)
let endLocation = locationConverter.location(for: node.rightBrace.endPositionBeforeTrailingTrivia)
return startLocation.line == endLocation.line ? .skipChildren : .visitChildren
}
override func visitPost(_ node: IdentifierExprSyntax) {
if case .dollarIdentifier = node.identifier.tokenKind {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
}

View File

@ -0,0 +1,62 @@
import SwiftSyntax
struct BlockBasedKVORule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "block_based_kvo",
name: "Block Based KVO",
description: "Prefer the new block based KVO API with keypaths when using Swift 3.2 or later",
kind: .idiomatic,
nonTriggeringExamples: [
Example(#"""
let observer = foo.observe(\.value, options: [.new]) { (foo, change) in
print(change.newValue)
}
"""#)
],
triggeringExamples: [
Example("""
class Foo: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: [NSKeyValueChangeKey : Any]?,
context: UnsafeMutableRawPointer?) {}
}
"""),
Example("""
class Foo: NSObject {
override func observeValue(forKeyPath keyPath: String?, of object: Any?,
change: Dictionary<NSKeyValueChangeKey, Any>?,
context: UnsafeMutableRawPointer?) {}
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension BlockBasedKVORule {
private final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionDeclSyntax) {
guard node.modifiers.containsOverride,
case let parameterList = node.signature.input.parameterList,
parameterList.count == 4,
node.identifier.text == "observeValue",
parameterList.map(\.firstName.text) == ["forKeyPath", "of", "change", "context"]
else {
return
}
let types = parameterList
.map { $0.type.trimmedDescription.replacingOccurrences(of: " ", with: "") }
let firstTypes = ["String?", "Any?", "[NSKeyValueChangeKey:Any]?", "UnsafeMutableRawPointer?"]
let secondTypes = ["String?", "Any?", "Dictionary<NSKeyValueChangeKey,Any>?", "UnsafeMutableRawPointer?"]
if types == firstTypes || types == secondTypes {
violations.append(node.funcKeyword.positionAfterSkippingLeadingTrivia)
}
}
}
}

View File

@ -0,0 +1,232 @@
import SwiftSyntax
struct ConvenienceTypeRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "convenience_type",
name: "Convenience Type",
description: "Types used for hosting only static members should be implemented as a caseless enum " +
"to avoid instantiation",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
enum Math { // enum
public static let pi = 3.14
}
"""),
Example("""
// class with inheritance
class MathViewController: UIViewController {
public static let pi = 3.14
}
"""),
Example("""
@objc class Math: NSObject { // class visible to Obj-C
public static let pi = 3.14
}
"""),
Example("""
struct Math { // type with non-static declarations
public static let pi = 3.14
public let randomNumber = 2
}
"""),
Example("class DummyClass {}"),
Example("""
class Foo: NSObject { // class with Obj-C class property
class @objc let foo = 1
}
"""),
Example("""
class Foo: NSObject { // class with Obj-C static property
static @objc let foo = 1
}
"""),
Example("""
class Foo { // @objc class func can't exist on an enum
@objc class func foo() {}
}
"""),
Example("""
class Foo { // @objc static func can't exist on an enum
@objc static func foo() {}
}
"""),
Example("""
@objcMembers class Foo { // @objc static func can't exist on an enum
static func foo() {}
}
"""),
Example("""
final class Foo { // final class, but @objc class func can't exist on an enum
@objc class func foo() {}
}
"""),
Example("""
final class Foo { // final class, but @objc static func can't exist on an enum
@objc static func foo() {}
}
"""),
Example("""
@globalActor actor MyActor {
static let shared = MyActor()
}
""")
],
triggeringExamples: [
Example("""
struct Math {
public static let pi = 3.14
}
"""),
Example("""
struct Math {
public static let pi = 3.14
@available(*, unavailable) init() {}
}
"""),
Example("""
final class Foo { // final class can't be inherited
class let foo = 1
}
"""),
// Intentional false positives. Non-final classes could be
// subclassed, but we figure it is probably rare enough that it is
// more important to catch these cases, and manually disable the
// rule if needed.
Example("""
class Foo {
class let foo = 1
}
"""),
Example("""
class Foo {
final class let foo = 1
}
"""),
Example("""
class SomeClass {
static func foo() {}
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension ConvenienceTypeRule {
final class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visitPost(_ node: StructDeclSyntax) {
if hasViolation(
inheritance: node.inheritanceClause,
attributes: node.attributes,
members: node.memberBlock
) {
violations.append(node.structKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: ClassDeclSyntax) {
if hasViolation(
inheritance: node.inheritanceClause,
attributes: node.attributes,
members: node.memberBlock
) {
violations.append(node.classKeyword.positionAfterSkippingLeadingTrivia)
}
}
private func hasViolation(inheritance: TypeInheritanceClauseSyntax?,
attributes: AttributeListSyntax?,
members: MemberDeclBlockSyntax) -> Bool {
guard inheritance.isNilOrEmpty,
!attributes.containsObjcMembers,
!attributes.containsObjc,
!members.members.isEmpty else {
return false
}
return ConvenienceTypeCheckVisitor(viewMode: .sourceAccurate)
.walk(tree: members, handler: \.canBeConvenienceType)
}
}
}
private class ConvenienceTypeCheckVisitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all }
private(set) var canBeConvenienceType = true
override func visitPost(_ node: VariableDeclSyntax) {
if node.isInstanceVariable {
canBeConvenienceType = false
} else if node.attributes.containsObjc {
canBeConvenienceType = false
}
}
override func visitPost(_ node: FunctionDeclSyntax) {
if node.modifiers.containsStaticOrClass {
if node.attributes.containsObjc {
canBeConvenienceType = false
}
} else {
canBeConvenienceType = false
}
}
override func visitPost(_ node: InitializerDeclSyntax) {
if !node.attributes.hasUnavailableAttribute {
canBeConvenienceType = false
}
}
override func visitPost(_ node: SubscriptDeclSyntax) {
if !node.modifiers.containsStaticOrClass {
canBeConvenienceType = false
}
}
}
private extension TypeInheritanceClauseSyntax? {
var isNilOrEmpty: Bool {
self?.inheritedTypeCollection.isEmpty ?? true
}
}
private extension AttributeListSyntax? {
var containsObjcMembers: Bool {
contains(attributeNamed: "objcMembers")
}
var containsObjc: Bool {
contains(attributeNamed: "objc")
}
}
private extension AttributeListSyntax? {
var hasUnavailableAttribute: Bool {
guard let attrs = self else {
return false
}
return attrs.contains { elem in
guard let attr = elem.as(AttributeSyntax.self),
let arguments = attr.argument?.as(AvailabilitySpecListSyntax.self) else {
return false
}
return attr.attributeNameText == "available" && arguments.contains { arg in
arg.entry.as(TokenSyntax.self)?.tokenKind.isUnavailableKeyword == true
}
}
}
}

View File

@ -0,0 +1,45 @@
import SwiftSyntax
struct DiscouragedAssertRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "discouraged_assert",
name: "Discouraged Assert",
description: "Prefer `assertionFailure()` and/or `preconditionFailure()` over `assert(false)`",
kind: .idiomatic,
nonTriggeringExamples: [
Example(#"assert(true)"#),
Example(#"assert(true, "foobar")"#),
Example(#"assert(true, "foobar", file: "toto", line: 42)"#),
Example(#"assert(false || true)"#),
Example(#"XCTAssert(false)"#)
],
triggeringExamples: [
Example(#"↓assert(false)"#),
Example(#"↓assert(false, "foobar")"#),
Example(#"↓assert(false, "foobar", file: "toto", line: 42)"#),
Example(#"↓assert( false , "foobar")"#)
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension DiscouragedAssertRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
guard node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text == "assert",
let firstArg = node.argumentList.first,
firstArg.label == nil,
let boolExpr = firstArg.expression.as(BooleanLiteralExprSyntax.self),
boolExpr.booleanLiteral.tokenKind == .keyword(.false) else {
return
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -1,11 +1,13 @@
import SourceKittenFramework
import SwiftSyntax
public struct DiscouragedNoneNameRule: ASTRule, OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public static var description = RuleDescription(
struct DiscouragedNoneNameRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static var description = RuleDescription(
identifier: "discouraged_none_name",
name: "Discouraged None Name",
description: "Discourages name cases/static members `none`, which can conflict with `Optional<T>.none`.",
description: "Enum cases and static members named `none` are discouraged as they can conflict with " +
"`Optional<T>.none`.",
kind: .idiomatic,
nonTriggeringExamples: [
// Should not trigger unless exactly matches "none"
@ -175,43 +177,61 @@ public struct DiscouragedNoneNameRule: ASTRule, OptInRule, ConfigurationProvider
]
)
public func validate(
file: SwiftLintFile,
kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary
) -> [StyleViolation] {
guard kind.isForValidating && dictionary.isNameInvalid, let offset = dictionary.offset else { return [] }
return [
StyleViolation(
ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset),
reason: kind.reason
)
]
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
public init() {}
}
private extension SwiftDeclarationKind {
var isForValidating: Bool { self == .enumelement || self == .varClass || self == .varStatic }
var reason: String {
var reasonPrefix: String {
switch self {
case .enumelement: return "`case`"
case .varClass: return "`class` member"
case .varStatic: return "`static` member"
default: return ""
private extension DiscouragedNoneNameRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: EnumCaseElementSyntax) {
let emptyParams = node.associatedValue?.parameterList.isEmpty ?? true
if emptyParams, node.identifier.isNone {
violations.append(ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: reason(type: "`case`")
))
}
}
let reason = "Avoid naming \(reasonPrefix) `none` as the compiler can think you mean `Optional<T>.none`."
let recommendation = "Consider using an Optional value instead."
return "\(reason) \(recommendation)"
override func visitPost(_ node: VariableDeclSyntax) {
let type: String? = {
if node.modifiers.isClass {
return "`class` member"
} else if node.modifiers.isStatic {
return "`static` member"
} else {
return nil
}
}()
guard let type else {
return
}
for binding in node.bindings {
guard let pattern = binding.pattern.as(IdentifierPatternSyntax.self), pattern.identifier.isNone else {
continue
}
violations.append(ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: reason(type: type)
))
return
}
}
private func reason(type: String) -> String {
let reason = "Avoid naming \(type) `none` as the compiler can think you mean `Optional<T>.none`"
let recommendation = "consider using an Optional value instead"
return "\(reason); \(recommendation)"
}
}
}
private extension SourceKittenDictionary {
var isNameInvalid: Bool { name == "none" }
private extension TokenSyntax {
var isNone: Bool {
tokenKind == .identifier("none") || tokenKind == .identifier("`none`")
}
}

View File

@ -0,0 +1,58 @@
import SwiftSyntax
struct DiscouragedObjectLiteralRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = DiscouragedObjectLiteralConfiguration()
static let description = RuleDescription(
identifier: "discouraged_object_literal",
name: "Discouraged Object Literal",
description: "Prefer initializers over object literals",
kind: .idiomatic,
nonTriggeringExamples: [
Example("let image = UIImage(named: aVariable)"),
Example("let image = UIImage(named: \"interpolated \\(variable)\")"),
Example("let color = UIColor(red: value, green: value, blue: value, alpha: 1)"),
Example("let image = NSImage(named: aVariable)"),
Example("let image = NSImage(named: \"interpolated \\(variable)\")"),
Example("let color = NSColor(red: value, green: value, blue: value, alpha: 1)")
],
triggeringExamples: [
Example("let image = ↓#imageLiteral(resourceName: \"image.jpg\")"),
Example("let color = ↓#colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1)")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(configuration: configuration)
}
}
private extension DiscouragedObjectLiteralRule {
final class Visitor: ViolationsSyntaxVisitor {
private let configuration: ConfigurationType
init(configuration: ConfigurationType) {
self.configuration = configuration
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: MacroExpansionExprSyntax) {
guard
case let .identifier(identifierText) = node.macro.tokenKind,
["colorLiteral", "imageLiteral"].contains(identifierText)
else {
return
}
if !configuration.imageLiteral && identifierText == "imageLiteral" {
return
}
if !configuration.colorLiteral && identifierText == "colorLiteral" {
return
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -0,0 +1,49 @@
import SwiftSyntax
struct DiscouragedOptionalBooleanRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "discouraged_optional_boolean",
name: "Discouraged Optional Boolean",
description: "Prefer non-optional booleans over optional booleans",
kind: .idiomatic,
nonTriggeringExamples: DiscouragedOptionalBooleanRuleExamples.nonTriggeringExamples,
triggeringExamples: DiscouragedOptionalBooleanRuleExamples.triggeringExamples
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension DiscouragedOptionalBooleanRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: OptionalTypeSyntax) {
if node.wrappedType.as(SimpleTypeIdentifierSyntax.self)?.typeName == "Bool" {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: OptionalChainingExprSyntax) {
if node.expression.as(IdentifierExprSyntax.self)?.identifier.text == "Bool" {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: FunctionCallExprSyntax) {
guard
let calledExpression = node.calledExpression.as(MemberAccessExprSyntax.self),
let singleArgument = node.argumentList.onlyElement,
singleArgument.expression.is(BooleanLiteralExprSyntax.self),
let base = calledExpression.base?.as(IdentifierExprSyntax.self),
base.identifier.text == "Optional",
calledExpression.name.text == "some"
else {
return
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -154,7 +154,10 @@ internal struct DiscouragedOptionalBooleanRuleExamples {
wrapExample("enum", "func foo(input: [↓Bool?]) {}"),
wrapExample("enum", "static func foo(input: ↓Bool?) {}"),
wrapExample("enum", "static func foo(input: [String: ↓Bool?]) {}"),
wrapExample("enum", "static func foo(input: [↓Bool?]) {}")
wrapExample("enum", "static func foo(input: [↓Bool?]) {}"),
// Optional chaining
Example("_ = ↓Bool?.values()")
]
}

View File

@ -1,23 +1,21 @@
import SourceKittenFramework
public struct DiscouragedOptionalCollectionRule: ASTRule, OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
struct DiscouragedOptionalCollectionRule: ASTRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "discouraged_optional_collection",
name: "Discouraged Optional Collection",
description: "Prefer empty collection over optional collection.",
description: "Prefer empty collection over optional collection",
kind: .idiomatic,
nonTriggeringExamples: DiscouragedOptionalCollectionExamples.nonTriggeringExamples,
triggeringExamples: DiscouragedOptionalCollectionExamples.triggeringExamples
)
public func validate(file: SwiftLintFile,
kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
let offsets = variableViolations(file: file, kind: kind, dictionary: dictionary) +
func validate(file: SwiftLintFile,
kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
let offsets = variableViolations(kind: kind, dictionary: dictionary) +
functionViolations(file: file, kind: kind, dictionary: dictionary)
return offsets.map {
@ -29,9 +27,7 @@ public struct DiscouragedOptionalCollectionRule: ASTRule, OptInRule, Configurati
// MARK: - Private
private func variableViolations(file: SwiftLintFile,
kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [ByteCount] {
private func variableViolations(kind: SwiftDeclarationKind, dictionary: SourceKittenDictionary) -> [ByteCount] {
guard
SwiftDeclarationKind.variableKinds.contains(kind),
let offset = dictionary.offset,

View File

@ -1,8 +1,8 @@
import Foundation
import SourceKittenFramework
public struct DuplicateImportsRule: ConfigurationProviderRule, CorrectableRule {
public var configuration = SeverityConfiguration(.warning)
struct DuplicateImportsRule: ConfigurationProviderRule, CorrectableRule {
var configuration = SeverityConfiguration<Self>(.warning)
// List of all possible import kinds
static let importKinds = [
@ -11,12 +11,10 @@ public struct DuplicateImportsRule: ConfigurationProviderRule, CorrectableRule {
"var", "func"
]
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "duplicate_imports",
name: "Duplicate Imports",
description: "Imports should be unique.",
description: "Imports should be unique",
kind: .idiomatic,
nonTriggeringExamples: DuplicateImportsRuleExamples.nonTriggeringExamples,
triggeringExamples: DuplicateImportsRuleExamples.triggeringExamples,
@ -132,7 +130,7 @@ public struct DuplicateImportsRule: ConfigurationProviderRule, CorrectableRule {
})
}
public func validate(file: SwiftLintFile) -> [StyleViolation] {
func validate(file: SwiftLintFile) -> [StyleViolation] {
return duplicateImports(file: file).map { duplicateImport in
StyleViolation(
ruleDescription: Self.description,
@ -142,7 +140,7 @@ public struct DuplicateImportsRule: ConfigurationProviderRule, CorrectableRule {
}
}
public func correct(file: SwiftLintFile) -> [Correction] {
func correct(file: SwiftLintFile) -> [Correction] {
let duplicateImports = duplicateImports(file: file).reversed().filter {
file.ruleEnabled(violatingRange: $0.range, for: self) != nil
}
@ -194,7 +192,7 @@ private extension Line {
/// For "import A.B.C" returns slices [["A", "B", "C"], ["A", "B"], ["A"]]
var importSlices: [ImportSlice] {
guard let importIdentifier = importIdentifier else { return [] }
guard let importIdentifier else { return [] }
let importedSubpathParts = importIdentifier.split(separator: ".").map { String($0) }
guard !importedSubpathParts.isEmpty else { return [] }

View File

@ -1,4 +1,3 @@
// swiftlint:disable type_body_length
internal struct DuplicateImportsRuleExamples {
static let nonTriggeringExamples = [
Example("""
@ -36,7 +35,7 @@ internal struct DuplicateImportsRuleExamples {
""")
]
static let triggeringExamples = Array(corrections.keys)
static let triggeringExamples = Array(corrections.keys.sorted())
static let corrections: [Example: Example] = {
var corrections = [

View File

@ -3,15 +3,13 @@ import SourceKittenFramework
private typealias SourceKittenElement = SourceKittenDictionary
public struct ExplicitACLRule: OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
struct ExplicitACLRule: OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "explicit_acl",
name: "Explicit ACL",
description: "All declarations should specify Access Control Level keywords explicitly.",
description: "All declarations should specify Access Control Level keywords explicitly",
kind: .idiomatic,
nonTriggeringExamples: [
Example("internal enum A {}\n"),
@ -122,7 +120,7 @@ public struct ExplicitACLRule: OptInRule, ConfigurationProviderRule {
}
}
public func validate(file: SwiftLintFile) -> [StyleViolation] {
func validate(file: SwiftLintFile) -> [StyleViolation] {
let implicitAndExplicitInternalElements = internalTypeElements(in: file.structureDictionary)
guard implicitAndExplicitInternalElements.isNotEmpty else {
@ -188,7 +186,7 @@ private extension SwiftDeclarationKind {
.functionMethodInstance, .functionMethodStatic, .functionOperator, .functionOperatorInfix,
.functionOperatorPostfix, .functionOperatorPrefix, .functionSubscript, .protocol, .opaqueType:
return true
case .class, .enum, .extension, .extensionClass, .extensionEnum,
case .actor, .class, .enum, .extension, .extensionClass, .extensionEnum,
.extensionProtocol, .extensionStruct, .struct:
return false
}

View File

@ -0,0 +1,105 @@
import SwiftSyntax
struct ExplicitEnumRawValueRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "explicit_enum_raw_value",
name: "Explicit Enum Raw Value",
description: "Enums should be explicitly assigned their raw values",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
enum Numbers {
case int(Int)
case short(Int16)
}
"""),
Example("""
enum Numbers: Int {
case one = 1
case two = 2
}
"""),
Example("""
enum Numbers: Double {
case one = 1.1
case two = 2.2
}
"""),
Example("""
enum Numbers: String {
case one = "one"
case two = "two"
}
"""),
Example("""
protocol Algebra {}
enum Numbers: Algebra {
case one
}
""")
],
triggeringExamples: [
Example("""
enum Numbers: Int {
case one = 10, two, three = 30
}
"""),
Example("""
enum Numbers: NSInteger {
case one
}
"""),
Example("""
enum Numbers: String {
case one
case two
}
"""),
Example("""
enum Numbers: String {
case one, two = "two"
}
"""),
Example("""
enum Numbers: Decimal {
case one, two
}
"""),
Example("""
enum Outer {
enum Numbers: Decimal {
case one, two
}
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension ExplicitEnumRawValueRule {
final class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visitPost(_ node: EnumCaseElementSyntax) {
if node.rawValue == nil, node.enclosingEnum()?.supportsRawValues == true {
violations.append(node.identifier.positionAfterSkippingLeadingTrivia)
}
}
}
}
private extension SyntaxProtocol {
func enclosingEnum() -> EnumDeclSyntax? {
if let node = self.as(EnumDeclSyntax.self) {
return node
}
return parent?.enclosingEnum()
}
}

View File

@ -0,0 +1,254 @@
import SwiftSyntax
import SwiftSyntaxBuilder
struct ExplicitInitRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "explicit_init",
name: "Explicit Init",
description: "Explicitly calling .init() should be avoided",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
import Foundation
class C: NSObject {
override init() {
super.init()
}
}
"""), // super
Example("""
struct S {
let n: Int
}
extension S {
init() {
self.init(n: 1)
}
}
"""), // self
Example("""
[1].flatMap(String.init)
"""), // pass init as closure
Example("""
[String.self].map { $0.init(1) }
"""), // initialize from a metatype value
Example("""
[String.self].map { type in type.init(1) }
"""), // initialize from a metatype value
Example("""
Observable.zip(obs1, obs2, resultSelector: MyType.init).asMaybe()
"""),
Example("_ = GleanMetrics.Tabs.someType.init()"),
Example("""
Observable.zip(
obs1,
obs2,
resultSelector: MyType.init
).asMaybe()
""")
],
triggeringExamples: [
Example("""
[1].flatMap{String.init($0)}
"""),
Example("""
[String.self].map { Type in Type.init(1) }
"""), // Starting with capital letter assumes a type
Example("""
func foo() -> [String] {
return [1].flatMap { String.init($0) }
}
"""),
Example("_ = GleanMetrics.Tabs.GroupedTabExtra↓.init()"),
Example("_ = Set<KsApi.Category>↓.init()"),
Example("""
Observable.zip(
obs1,
obs2,
resultSelector: { MyType.init($0, $1) }
).asMaybe()
"""),
Example("""
let int = In🤓t
.init(1.0)
""", excludeFromDocumentation: true),
Example("""
let int = Int
.init(1.0)
""", excludeFromDocumentation: true),
Example("""
let int = Int
.init(1.0)
""", excludeFromDocumentation: true)
],
corrections: [
Example("""
[1].flatMap{String.init($0)}
"""):
Example("""
[1].flatMap{String($0)}
"""),
Example("""
func foo() -> [String] {
return [1].flatMap { String.init($0) }
}
"""):
Example("""
func foo() -> [String] {
return [1].flatMap { String($0) }
}
"""),
Example("""
class C {
#if true
func f() {
[1].flatMap{String.init($0)}
}
#endif
}
"""):
Example("""
class C {
#if true
func f() {
[1].flatMap{String($0)}
}
#endif
}
"""),
Example("""
let int = Int
.init(1.0)
"""):
Example("""
let int = Int(1.0)
"""),
Example("""
let int = Int
.init(1.0)
"""):
Example("""
let int = Int(1.0)
"""),
Example("""
let int = Int
.init(1.0)
"""):
Example("""
let int = Int(1.0)
"""),
Example("""
let int = Int
.init(1.0)
"""):
Example("""
let int = Int(1.0)
"""),
Example("_ = GleanMetrics.Tabs.GroupedTabExtra↓.init()"):
Example("_ = GleanMetrics.Tabs.GroupedTabExtra()"),
Example("_ = Set<KsApi.Category>↓.init()"):
Example("_ = Set<KsApi.Category>()")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private extension ExplicitInitRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
guard
let calledExpression = node.calledExpression.as(MemberAccessExprSyntax.self),
let violationPosition = calledExpression.explicitInitPosition
else {
return
}
violations.append(violationPosition)
}
}
final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
guard
let calledExpression = node.calledExpression.as(MemberAccessExprSyntax.self),
let violationPosition = calledExpression.explicitInitPosition,
let calledBase = calledExpression.base,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
else {
return super.visit(node)
}
correctionPositions.append(violationPosition)
let newNode = node.with(\.calledExpression, calledBase.trimmed)
return super.visit(newNode)
}
}
}
private extension MemberAccessExprSyntax {
var explicitInitPosition: AbsolutePosition? {
if let base, base.isTypeReferenceLike, name.text == "init" {
return base.endPositionBeforeTrailingTrivia
} else {
return nil
}
}
}
private extension ExprSyntax {
/// `String` or `Nested.Type`.
var isTypeReferenceLike: Bool {
if let expr = self.as(IdentifierExprSyntax.self), expr.identifier.text.startsWithUppercase {
return true
} else if let expr = self.as(MemberAccessExprSyntax.self),
expr.description.split(separator: ".").allSatisfy(\.startsWithUppercase) {
return true
} else if let expr = self.as(SpecializeExprSyntax.self)?.expression.as(IdentifierExprSyntax.self),
expr.identifier.text.startsWithUppercase {
return true
} else {
return false
}
}
}
private extension StringProtocol {
var startsWithUppercase: Bool { first?.isUppercase == true }
}

View File

@ -0,0 +1,118 @@
import SwiftSyntax
struct ExplicitTopLevelACLRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "explicit_top_level_acl",
name: "Explicit Top Level ACL",
description: "Top-level declarations should specify Access Control Level keywords explicitly",
kind: .idiomatic,
nonTriggeringExamples: [
Example("internal enum A {}\n"),
Example("public final class B {}\n"),
Example("private struct C {}\n"),
Example("internal enum A {\n enum B {}\n}"),
Example("internal final class Foo {}"),
Example("internal\nclass Foo {}"),
Example("internal func a() {}\n"),
Example("extension A: Equatable {}"),
Example("extension A {}")
],
triggeringExamples: [
Example("↓enum A {}\n"),
Example("final ↓class B {}\n"),
Example("↓struct C {}\n"),
Example("↓func a() {}\n"),
Example("internal let a = 0\n↓func b() {}\n")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension ExplicitTopLevelACLRule {
final class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all }
override func visitPost(_ node: ClassDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.classKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: StructDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.structKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: EnumDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.enumKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: ProtocolDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.protocolKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: ActorDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.actorKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: TypealiasDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.typealiasKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: FunctionDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.funcKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: VariableDeclSyntax) {
if hasViolation(modifiers: node.modifiers) {
violations.append(node.bindingKeyword.positionAfterSkippingLeadingTrivia)
}
}
override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind {
.skipChildren
}
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
.skipChildren
}
private func hasViolation(modifiers: ModifierListSyntax?) -> Bool {
guard let modifiers else {
return true
}
return !modifiers.contains(where: \.isACLModifier)
}
}
}
private extension DeclModifierSyntax {
var isACLModifier: Bool {
let aclModifiers: Set<TokenKind> = [
.keyword(.private),
.keyword(.fileprivate),
.keyword(.internal),
.keyword(.public),
.keyword(.open)
]
return detail == nil && aclModifiers.contains(name.tokenKind)
}
}

View File

@ -0,0 +1,142 @@
import SwiftSyntax
struct ExplicitTypeInterfaceRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = ExplicitTypeInterfaceConfiguration()
static let description = RuleDescription(
identifier: "explicit_type_interface",
name: "Explicit Type Interface",
description: "Properties should have a type interface",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
class Foo {
var myVar: Int? = 0
}
"""),
Example("""
class Foo {
let myVar: Int? = 0, s: String = ""
}
"""),
Example("""
class Foo {
static var myVar: Int? = 0
}
"""),
Example("""
class Foo {
class var myVar: Int? = 0
}
"""),
Example("""
func f() {
if case .failure(let error) = errorCompletion {}
}
""", excludeFromDocumentation: true)
],
triggeringExamples: [
Example("""
class Foo {
var myVar = 0
}
"""),
Example("""
class Foo {
let mylet = 0
}
"""),
Example("""
class Foo {
static var myStaticVar = 0
}
"""),
Example("""
class Foo {
class var myClassVar = 0
}
"""),
Example("""
class Foo {
let myVar = Int(0), s = ""
}
"""),
Example("""
class Foo {
let myVar = Set<Int>(0)
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(configuration: configuration)
}
}
private class Visitor: ViolationsSyntaxVisitor {
let configuration: ExplicitTypeInterfaceConfiguration
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
init(configuration: ExplicitTypeInterfaceConfiguration) {
self.configuration = configuration
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: VariableDeclSyntax) {
if node.modifiers.isClass {
if configuration.allowedKinds.contains(.class) {
checkViolation(node)
}
} else if node.modifiers.isStatic {
if configuration.allowedKinds.contains(.static) {
checkViolation(node)
}
} else if node.parent?.is(MemberDeclListItemSyntax.self) == true {
if configuration.allowedKinds.contains(.instance) {
checkViolation(node)
}
} else if node.parent?.is(CodeBlockItemSyntax.self) == true {
if configuration.allowedKinds.contains(.local) {
checkViolation(node)
}
}
}
private func checkViolation(_ node: VariableDeclSyntax) {
for binding in node.bindings {
if configuration.allowRedundancy, let initializer = binding.initializer,
initializer.isTypeConstructor || initializer.isTypeReference {
continue
}
if binding.typeAnnotation == nil {
violations.append(binding.positionAfterSkippingLeadingTrivia)
}
}
}
}
private extension InitializerClauseSyntax {
var isTypeConstructor: Bool {
if value.as(FunctionCallExprSyntax.self)?.callsPotentialType == true {
return true
}
if let tryExpr = value.as(TryExprSyntax.self),
tryExpr.expression.as(FunctionCallExprSyntax.self)?.callsPotentialType == true {
return true
}
return false
}
var isTypeReference: Bool {
value.as(MemberAccessExprSyntax.self)?.name.tokenKind == .keyword(.self)
}
}
private extension FunctionCallExprSyntax {
var callsPotentialType: Bool {
let name = calledExpression.debugDescription
return name.first?.isUppercase == true || (name.first == "[" && name.last == "]")
}
}

View File

@ -1,12 +1,10 @@
import Foundation
import SourceKittenFramework
public struct ExtensionAccessModifierRule: ASTRule, ConfigurationProviderRule, OptInRule {
public var configuration = SeverityConfiguration(.warning)
struct ExtensionAccessModifierRule: ASTRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "extension_access_modifier",
name: "Extension Access Modifier",
description: "Prefer to use extension access modifiers",
@ -92,8 +90,8 @@ public struct ExtensionAccessModifierRule: ASTRule, ConfigurationProviderRule, O
]
)
public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard kind == .extension, let offset = dictionary.offset,
dictionary.inheritedTypes.isEmpty
else {
@ -157,9 +155,7 @@ public struct ExtensionAccessModifierRule: ASTRule, ConfigurationProviderRule, O
// attributeBuiltin (`final` for example) tokens between them
let length = typeOffset - previousInternalByteRange.location
let range = ByteRange(location: previousInternalByteRange.location, length: length)
let internalBelongsToType = Set(file.syntaxMap.kinds(inByteRange: range)) == [.attributeBuiltin]
return internalBelongsToType
return Set(file.syntaxMap.kinds(inByteRange: range)) == [.attributeBuiltin]
}
return violationOffsets.map {

View File

@ -0,0 +1,42 @@
import SwiftSyntax
struct FallthroughRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "fallthrough",
name: "Fallthrough",
description: "Fallthrough should be avoided",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
switch foo {
case .bar, .bar2, .bar3:
something()
}
""")
],
triggeringExamples: [
Example("""
switch foo {
case .bar:
fallthrough
case .bar2:
something()
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension FallthroughRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FallthroughStmtSyntax) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -0,0 +1,63 @@
import SwiftSyntax
struct FatalErrorMessageRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "fatal_error_message",
name: "Fatal Error Message",
description: "A fatalError call should have a message",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
func foo() {
fatalError("Foo")
}
"""),
Example("""
func foo() {
fatalError(x)
}
""")
],
triggeringExamples: [
Example("""
func foo() {
fatalError("")
}
"""),
Example("""
func foo() {
fatalError()
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension FatalErrorMessageRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
guard let expression = node.calledExpression.as(IdentifierExprSyntax.self),
expression.identifier.text == "fatalError",
node.argumentList.isEmptyOrEmptyString else {
return
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
private extension TupleExprElementListSyntax {
var isEmptyOrEmptyString: Bool {
if isEmpty {
return true
}
return count == 1 && first?.expression.as(StringLiteralExprSyntax.self)?.isEmptyString == true
}
}

View File

@ -1,22 +1,17 @@
import Foundation
import SourceKittenFramework
public struct FileNameNoSpaceRule: ConfigurationProviderRule, OptInRule {
public var configuration = FileNameNoSpaceConfiguration(
severity: .warning,
excluded: []
)
struct FileNameNoSpaceRule: ConfigurationProviderRule, OptInRule, SourceKitFreeRule {
var configuration = FileNameNoSpaceConfiguration()
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "file_name_no_space",
name: "File Name No Space",
description: "File name should not contain any whitespace.",
name: "File Name no Space",
description: "File name should not contain any whitespace",
kind: .idiomatic
)
public func validate(file: SwiftLintFile) -> [StyleViolation] {
func validate(file: SwiftLintFile) -> [StyleViolation] {
guard let filePath = file.path,
case let fileName = filePath.bridge().lastPathComponent,
!configuration.excluded.contains(fileName),
@ -25,7 +20,7 @@ public struct FileNameNoSpaceRule: ConfigurationProviderRule, OptInRule {
}
return [StyleViolation(ruleDescription: Self.description,
severity: configuration.severity.severity,
severity: configuration.severity,
location: Location(file: filePath, line: 1))]
}
}

View File

@ -0,0 +1,84 @@
import SwiftSyntax
struct FileNameRule: ConfigurationProviderRule, OptInRule, SourceKitFreeRule {
var configuration = FileNameConfiguration()
static let description = RuleDescription(
identifier: "file_name",
name: "File Name",
description: "File name should match a type or extension declared in the file (if any)",
kind: .idiomatic
)
func validate(file: SwiftLintFile) -> [StyleViolation] {
guard let filePath = file.path,
case let fileName = filePath.bridge().lastPathComponent,
!configuration.excluded.contains(fileName) else {
return []
}
let prefixRegex = regex("\\A(?:\(configuration.prefixPattern))")
let suffixRegex = regex("(?:\(configuration.suffixPattern))\\z")
var typeInFileName = fileName.bridge().deletingPathExtension
// Process prefix
if let match = prefixRegex.firstMatch(in: typeInFileName, options: [], range: typeInFileName.fullNSRange),
let range = typeInFileName.nsrangeToIndexRange(match.range) {
typeInFileName.removeSubrange(range)
}
// Process suffix
if let match = suffixRegex.firstMatch(in: typeInFileName, options: [], range: typeInFileName.fullNSRange),
let range = typeInFileName.nsrangeToIndexRange(match.range) {
typeInFileName.removeSubrange(range)
}
// Process nested type separator
let allDeclaredTypeNames = TypeNameCollectingVisitor(viewMode: .sourceAccurate)
.walk(tree: file.syntaxTree, handler: \.names)
.map {
$0.replacingOccurrences(of: ".", with: configuration.nestedTypeSeparator)
}
guard allDeclaredTypeNames.isNotEmpty, !allDeclaredTypeNames.contains(typeInFileName) else {
return []
}
return [StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: filePath, line: 1))]
}
}
private class TypeNameCollectingVisitor: SyntaxVisitor {
private(set) var names: Set<String> = []
override func visitPost(_ node: ClassDeclSyntax) {
names.insert(node.identifier.text)
}
override func visitPost(_ node: ActorDeclSyntax) {
names.insert(node.identifier.text)
}
override func visitPost(_ node: StructDeclSyntax) {
names.insert(node.identifier.text)
}
override func visitPost(_ node: TypealiasDeclSyntax) {
names.insert(node.identifier.text)
}
override func visitPost(_ node: EnumDeclSyntax) {
names.insert(node.identifier.text)
}
override func visitPost(_ node: ProtocolDeclSyntax) {
names.insert(node.identifier.text)
}
override func visitPost(_ node: ExtensionDeclSyntax) {
names.insert(node.extendedType.trimmedDescription)
}
}

View File

@ -0,0 +1,189 @@
import SwiftSyntax
struct ForWhereRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = ForWhereConfiguration()
static let description = RuleDescription(
identifier: "for_where",
name: "Prefer For-Where",
description: "`where` clauses are preferred over a single `if` inside a `for`",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
for user in users where user.id == 1 { }
"""),
// if let
Example("""
for user in users {
if let id = user.id { }
}
"""),
// if var
Example("""
for user in users {
if var id = user.id { }
}
"""),
// if with else
Example("""
for user in users {
if user.id == 1 { } else { }
}
"""),
// if with else if
Example("""
for user in users {
if user.id == 1 {
} else if user.id == 2 { }
}
"""),
// if is not the only expression inside for
Example("""
for user in users {
if user.id == 1 { }
print(user)
}
"""),
// if a variable is used
Example("""
for user in users {
let id = user.id
if id == 1 { }
}
"""),
// if something is after if
Example("""
for user in users {
if user.id == 1 { }
return true
}
"""),
// condition with multiple clauses
Example("""
for user in users {
if user.id == 1 && user.age > 18 { }
}
"""),
Example("""
for user in users {
if user.id == 1, user.age > 18 { }
}
"""),
// if case
Example("""
for (index, value) in array.enumerated() {
if case .valueB(_) = value {
return index
}
}
"""),
Example("""
for user in users {
if user.id == 1 { return true }
}
""", configuration: ["allow_for_as_filter": true]),
Example("""
for user in users {
if user.id == 1 {
let derivedValue = calculateValue(from: user)
return derivedValue != 0
}
}
""", configuration: ["allow_for_as_filter": true])
],
triggeringExamples: [
Example("""
for user in users {
if user.id == 1 { return true }
}
"""),
Example("""
for subview in subviews {
if !(subview is UIStackView) {
subview.removeConstraints(subview.constraints)
subview.removeFromSuperview()
}
}
"""),
Example("""
for subview in subviews {
if !(subview is UIStackView) {
subview.removeConstraints(subview.constraints)
subview.removeFromSuperview()
}
}
""", configuration: ["allow_for_as_filter": true])
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(allowForAsFilter: configuration.allowForAsFilter)
}
}
private extension ForWhereRule {
final class Visitor: ViolationsSyntaxVisitor {
private let allowForAsFilter: Bool
init(allowForAsFilter: Bool) {
self.allowForAsFilter = allowForAsFilter
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: ForInStmtSyntax) {
guard node.whereClause == nil,
let onlyExprStmt = node.body.statements.onlyElement?.item.as(ExpressionStmtSyntax.self),
let ifExpr = onlyExprStmt.expression.as(IfExprSyntax.self),
ifExpr.elseBody == nil,
!ifExpr.containsOptionalBinding,
!ifExpr.containsPatternCondition,
let condition = ifExpr.conditions.onlyElement,
!condition.containsMultipleConditions else {
return
}
if allowForAsFilter, ifExpr.containsReturnStatement {
return
}
violations.append(ifExpr.positionAfterSkippingLeadingTrivia)
}
}
}
private extension IfExprSyntax {
var containsOptionalBinding: Bool {
conditions.contains { element in
element.condition.is(OptionalBindingConditionSyntax.self)
}
}
var containsPatternCondition: Bool {
conditions.contains { element in
element.condition.is(MatchingPatternConditionSyntax.self)
}
}
var containsReturnStatement: Bool {
body.statements.contains { element in
element.item.is(ReturnStmtSyntax.self)
}
}
}
private extension ConditionElementSyntax {
var containsMultipleConditions: Bool {
guard let condition = condition.as(SequenceExprSyntax.self) else {
return false
}
return condition.elements.contains { expr in
guard let binaryExpr = expr.as(BinaryOperatorExprSyntax.self) else {
return false
}
let operators: Set = ["&&", "||"]
return operators.contains(binaryExpr.operatorToken.text)
}
}
}

View File

@ -0,0 +1,34 @@
import SwiftSyntax
struct ForceCastRule: ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration<Self>(.error)
static let description = RuleDescription(
identifier: "force_cast",
name: "Force Cast",
description: "Force casts should be avoided",
kind: .idiomatic,
nonTriggeringExamples: [
Example("NSNumber() as? Int\n")
],
triggeringExamples: [ Example("NSNumber() ↓as! Int\n") ]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
ForceCastRuleVisitor(viewMode: .sourceAccurate)
}
}
private final class ForceCastRuleVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: AsExprSyntax) {
if node.questionOrExclamationMark?.tokenKind == .exclamationMark {
violations.append(node.asTok.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: UnresolvedAsExprSyntax) {
if node.questionOrExclamationMark?.tokenKind == .exclamationMark {
violations.append(node.asTok.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -0,0 +1,40 @@
import SwiftSyntax
struct ForceTryRule: ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration<Self>(.error)
static let description = RuleDescription(
identifier: "force_try",
name: "Force Try",
description: "Force tries should be avoided",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
func a() throws {}
do {
try a()
} catch {}
""")
],
triggeringExamples: [
Example("""
func a() throws {}
try! a()
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension ForceTryRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: TryExprSyntax) {
if node.questionOrExclamationMark?.tokenKind == .exclamationMark {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
}

View File

@ -0,0 +1,77 @@
import SwiftSyntax
struct ForceUnwrappingRule: OptInRule, SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "force_unwrapping",
name: "Force Unwrapping",
description: "Force unwrapping should be avoided",
kind: .idiomatic,
nonTriggeringExamples: [
Example("if let url = NSURL(string: query)"),
Example("navigationController?.pushViewController(viewController, animated: true)"),
Example("let s as! Test"),
Example("try! canThrowErrors()"),
Example("let object: Any!"),
Example("@IBOutlet var constraints: [NSLayoutConstraint]!"),
Example("setEditing(!editing, animated: true)"),
Example("navigationController.setNavigationBarHidden(!navigationController." +
"navigationBarHidden, animated: true)"),
Example("if addedToPlaylist && (!self.selectedFilters.isEmpty || " +
"self.searchBar?.text?.isEmpty == false) {}"),
Example("print(\"\\(xVar)!\")"),
Example("var test = (!bar)"),
Example("var a: [Int]!"),
Example("private var myProperty: (Void -> Void)!"),
Example("func foo(_ options: [AnyHashable: Any]!) {"),
Example("func foo() -> [Int]!"),
Example("func foo() -> [AnyHashable: Any]!"),
Example("func foo() -> [Int]! { return [] }"),
Example("return self")
],
triggeringExamples: [
Example("let url = NSURL(string: query)↓!"),
Example("navigationController↓!.pushViewController(viewController, animated: true)"),
Example("let unwrapped = optional↓!"),
Example("return cell↓!"),
Example("let url = NSURL(string: \"http://www.google.com\")↓!"),
Example("""
let dict = ["Boooo": "👻"]
func bla() -> String {
return dict["Boooo"]!
}
"""),
Example("""
let dict = ["Boooo": "👻"]
func bla() -> String {
return dict["Boooo"]!.contains("B")
}
"""),
Example("let a = dict[\"abc\"]↓!.contains(\"B\")"),
Example("dict[\"abc\"]↓!.bar(\"B\")"),
Example("if dict[\"a\"]↓!↓!↓!↓! {}"),
Example("var foo: [Bool]! = dict[\"abc\"]↓!"),
Example("realm.objects(SwiftUTF8Object.self).filter(\"%K == %@\", \"柱нǢкƱаم👍\", utf8TestString).first↓!"),
Example("""
context("abc") {
var foo: [Bool]! = dict["abc"]!
}
"""),
Example("open var computed: String { return foo.bar↓! }"),
Example("return self↓!"),
Example("[1, 3, 5, 6].first { $0.isMultiple(of: 2) }↓!"),
Example("map[\"a\"]↓!↓!")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
ForceUnwrappingVisitor(viewMode: .sourceAccurate)
}
}
private final class ForceUnwrappingVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: ForcedValueExprSyntax) {
violations.append(node.exclamationMark.positionAfterSkippingLeadingTrivia)
}
}

View File

@ -0,0 +1,132 @@
import SwiftSyntax
struct FunctionDefaultParameterAtEndRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "function_default_parameter_at_end",
name: "Function Default Parameter at End",
description: "Prefer to locate parameters with defaults toward the end of the parameter list",
kind: .idiomatic,
nonTriggeringExamples: [
Example("func foo(baz: String, bar: Int = 0) {}"),
Example("func foo(x: String, y: Int = 0, z: CGFloat = 0) {}"),
Example("func foo(bar: String, baz: Int = 0, z: () -> Void) {}"),
Example("func foo(bar: String, z: () -> Void, baz: Int = 0) {}"),
Example("func foo(bar: Int = 0) {}"),
Example("func foo() {}"),
Example("""
class A: B {
override func foo(bar: Int = 0, baz: String) {}
"""),
Example("func foo(bar: Int = 0, completion: @escaping CompletionHandler) {}"),
Example("""
func foo(a: Int, b: CGFloat = 0) {
let block = { (error: Error?) in }
}
"""),
Example("""
func foo(a: String, b: String? = nil,
c: String? = nil, d: @escaping AlertActionHandler = { _ in }) {}
"""),
Example("override init?(for date: Date = Date(), coordinate: CLLocationCoordinate2D) {}"),
Example("""
func handleNotification(_ userInfo: NSDictionary,
userInteraction: Bool = false,
completionHandler: ((UIBackgroundFetchResult) -> Void)?) {}
"""),
Example("""
func write(withoutNotifying tokens: [NotificationToken] = {}, _ block: (() throws -> Int)) {}
"""),
Example("""
func expect<T>(file: String = #file, _ expression: @autoclosure () -> (() throws -> T)) -> Expectation<T> {}
""", excludeFromDocumentation: true)
],
triggeringExamples: [
Example("↓func foo(bar: Int = 0, baz: String) {}"),
Example("private ↓func foo(bar: Int = 0, baz: String) {}"),
Example("public ↓init?(for date: Date = Date(), coordinate: CLLocationCoordinate2D) {}")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension FunctionDefaultParameterAtEndRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionDeclSyntax) {
guard !node.modifiers.containsOverride, node.signature.containsViolation else {
return
}
violations.append(node.funcKeyword.positionAfterSkippingLeadingTrivia)
}
override func visitPost(_ node: InitializerDeclSyntax) {
guard !node.modifiers.containsOverride, node.signature.containsViolation else {
return
}
violations.append(node.initKeyword.positionAfterSkippingLeadingTrivia)
}
}
}
private extension FunctionSignatureSyntax {
var containsViolation: Bool {
let params = input.parameterList.filter { param in
!param.isClosure
}
guard params.isNotEmpty else {
return false
}
let defaultParams = params.filter { param in
param.defaultArgument != nil
}
guard defaultParams.isNotEmpty else {
return false
}
let lastParameters = params.suffix(defaultParams.count)
let lastParametersWithDefaultValue = lastParameters.filter { param in
param.defaultArgument != nil
}
return lastParameters.count != lastParametersWithDefaultValue.count
}
}
private extension FunctionParameterSyntax {
var isClosure: Bool {
if isEscaping || type.is(FunctionTypeSyntax.self) {
return true
}
if let optionalType = type.as(OptionalTypeSyntax.self),
let tuple = optionalType.wrappedType.as(TupleTypeSyntax.self) {
return tuple.elements.onlyElement?.type.as(FunctionTypeSyntax.self) != nil
}
if let tuple = type.as(TupleTypeSyntax.self) {
return tuple.elements.onlyElement?.type.as(FunctionTypeSyntax.self) != nil
}
if let attrType = type.as(AttributedTypeSyntax.self) {
return attrType.baseType.is(FunctionTypeSyntax.self)
}
return false
}
var isEscaping: Bool {
guard let attrType = type.as(AttributedTypeSyntax.self) else {
return false
}
return attrType.attributes.contains(attributeNamed: "escaping")
}
}

View File

@ -0,0 +1,101 @@
import Foundation
import SwiftSyntax
struct GenericTypeNameRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = NameConfiguration<Self>(minLengthWarning: 1,
minLengthError: 0,
maxLengthWarning: 20,
maxLengthError: 1000)
static let description = RuleDescription(
identifier: "generic_type_name",
name: "Generic Type Name",
description: "Generic type name should only contain alphanumeric characters, start with an " +
"uppercase character and span between 1 and 20 characters in length.",
kind: .idiomatic,
nonTriggeringExamples: [
Example("func foo<T>() {}\n"),
Example("func foo<T>() -> T {}\n"),
Example("func foo<T, U>(param: U) -> T {}\n"),
Example("func foo<T: Hashable, U: Rule>(param: U) -> T {}\n"),
Example("struct Foo<T> {}\n"),
Example("class Foo<T> {}\n"),
Example("enum Foo<T> {}\n"),
Example("func run(_ options: NoOptions<CommandantError<()>>) {}\n"),
Example("func foo(_ options: Set<type>) {}\n"),
Example("func < <T: Comparable>(lhs: T?, rhs: T?) -> Bool\n"),
Example("func configureWith(data: Either<MessageThread, (project: Project, backing: Backing)>)\n"),
Example("typealias StringDictionary<T> = Dictionary<String, T>\n"),
Example("typealias BackwardTriple<T1, T2, T3> = (T3, T2, T1)\n"),
Example("typealias DictionaryOfStrings<T : Hashable> = Dictionary<T, String>\n")
],
triggeringExamples: [
Example("func foo<↓T_Foo>() {}\n"),
Example("func foo<T, ↓U_Foo>(param: U_Foo) -> T {}\n"),
Example("func foo<↓\(String(repeating: "T", count: 21))>() {}\n"),
Example("func foo<↓type>() {}\n"),
Example("typealias StringDictionary<↓T_Foo> = Dictionary<String, T_Foo>\n"),
Example("typealias BackwardTriple<T1, ↓T2_Bar, T3> = (T3, T2_Bar, T1)\n"),
Example("typealias DictionaryOfStrings<↓T_Foo: Hashable> = Dictionary<T_Foo, String>\n")
] + ["class", "struct", "enum"].flatMap { type -> [Example] in
return [
Example("\(type) Foo<↓T_Foo> {}\n"),
Example("\(type) Foo<T, ↓U_Foo> {}\n"),
Example("\(type) Foo<↓T_Foo, ↓U_Foo> {}\n"),
Example("\(type) Foo<↓\(String(repeating: "T", count: 21))> {}\n"),
Example("\(type) Foo<↓type> {}\n")
]
}
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(configuration: configuration)
}
}
private extension GenericTypeNameRule {
final class Visitor: ViolationsSyntaxVisitor {
private let configuration: ConfigurationType
init(configuration: ConfigurationType) {
self.configuration = configuration
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: GenericParameterSyntax) {
let name = node.name.text
guard !name.isEmpty, !configuration.shouldExclude(name: name) else { return }
if !configuration.allowedSymbolsAndAlphanumerics.isSuperset(of: CharacterSet(charactersIn: name)) {
violations.append(
ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: """
Generic type name '\(name)' should only contain alphanumeric and other allowed characters
""",
severity: .error
)
)
} else if let caseCheckSeverity = configuration.validatesStartWithLowercase.severity,
!String(name[name.startIndex]).isUppercase() {
violations.append(
ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: "Generic type name '\(name)' should start with an uppercase character",
severity: caseCheckSeverity
)
)
} else if let severity = configuration.severity(forLength: name.count) {
let reason = "Generic type name '\(name)' should be between \(configuration.minLengthThreshold) and " +
"\(configuration.maxLengthThreshold) characters long"
violations.append(
ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: reason,
severity: severity
)
)
}
}
}
}

View File

@ -0,0 +1,71 @@
import SwiftSyntax
struct ImplicitlyUnwrappedOptionalRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = ImplicitlyUnwrappedOptionalConfiguration()
static let description = RuleDescription(
identifier: "implicitly_unwrapped_optional",
name: "Implicitly Unwrapped Optional",
description: "Implicitly unwrapped optionals should be avoided when possible",
kind: .idiomatic,
nonTriggeringExamples: [
Example("@IBOutlet private var label: UILabel!"),
Example("@IBOutlet var label: UILabel!"),
Example("@IBOutlet var label: [UILabel!]"),
Example("if !boolean {}"),
Example("let int: Int? = 42"),
Example("let int: Int? = nil"),
Example("""
class MyClass {
@IBOutlet
weak var bar: SomeObject!
}
""", configuration: ["mode": "all_except_iboutlets"], excludeFromDocumentation: true)
],
triggeringExamples: [
Example("let label: ↓UILabel!"),
Example("let IBOutlet: ↓UILabel!"),
Example("let labels: [↓UILabel!]"),
Example("var ints: [↓Int!] = [42, nil, 42]"),
Example("let label: ↓IBOutlet!"),
Example("let int: ↓Int! = 42"),
Example("let int: ↓Int! = nil"),
Example("var int: ↓Int! = 42"),
Example("let collection: AnyCollection<↓Int!>"),
Example("func foo(int: ↓Int!) {}"),
Example("""
class MyClass {
weak var bar: SomeObject!
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(mode: configuration.mode)
}
}
private extension ImplicitlyUnwrappedOptionalRule {
final class Visitor: ViolationsSyntaxVisitor {
private let mode: ConfigurationType.ImplicitlyUnwrappedOptionalModeConfiguration
init(mode: ConfigurationType.ImplicitlyUnwrappedOptionalModeConfiguration) {
self.mode = mode
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: ImplicitlyUnwrappedOptionalTypeSyntax) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
switch mode {
case .all:
return .visitChildren
case .allExceptIBOutlets:
return node.isIBOutlet ? .skipChildren : .visitChildren
}
}
}
}

View File

@ -0,0 +1,43 @@
import SwiftSyntax
struct IsDisjointRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "is_disjoint",
name: "Is Disjoint",
description: "Prefer using `Set.isDisjoint(with:)` over `Set.intersection(_:).isEmpty`",
kind: .idiomatic,
nonTriggeringExamples: [
Example("_ = Set(syntaxKinds).isDisjoint(with: commentAndStringKindsSet)"),
Example("let isObjc = !objcAttributes.isDisjoint(with: dictionary.enclosedSwiftAttributes)"),
Example("_ = Set(syntaxKinds).intersection(commentAndStringKindsSet)"),
Example("_ = !objcAttributes.intersection(dictionary.enclosedSwiftAttributes)")
],
triggeringExamples: [
Example("_ = Set(syntaxKinds).↓intersection(commentAndStringKindsSet).isEmpty"),
Example("let isObjc = !objcAttributes.↓intersection(dictionary.enclosedSwiftAttributes).isEmpty")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension IsDisjointRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: MemberAccessExprSyntax) {
guard
node.name.text == "isEmpty",
let firstBase = node.base?.asFunctionCall,
let firstBaseCalledExpression = firstBase.calledExpression.as(MemberAccessExprSyntax.self),
firstBaseCalledExpression.name.text == "intersection"
else {
return
}
violations.append(firstBaseCalledExpression.name.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -0,0 +1,96 @@
import SwiftSyntax
struct JoinedDefaultParameterRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "joined_default_parameter",
name: "Joined Default Parameter",
description: "Discouraged explicit usage of the default separator",
kind: .idiomatic,
nonTriggeringExamples: [
Example("let foo = bar.joined()"),
Example("let foo = bar.joined(separator: \",\")"),
Example("let foo = bar.joined(separator: toto)")
],
triggeringExamples: [
Example("let foo = bar.joined(↓separator: \"\")"),
Example("""
let foo = bar.filter(toto)
.joined(separator: ""),
"""),
Example("""
func foo() -> String {
return ["1", "2"].joined(separator: "")
}
""")
],
corrections: [
Example("let foo = bar.joined(↓separator: \"\")"): Example("let foo = bar.joined()"),
Example("let foo = bar.filter(toto)\n.joined(↓separator: \"\")"):
Example("let foo = bar.filter(toto)\n.joined()"),
Example("func foo() -> String {\n return [\"1\", \"2\"].joined(↓separator: \"\")\n}"):
Example("func foo() -> String {\n return [\"1\", \"2\"].joined()\n}"),
Example("class C {\n#if true\nlet foo = bar.joined(↓separator: \"\")\n#endif\n}"):
Example("class C {\n#if true\nlet foo = bar.joined()\n#endif\n}")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private extension JoinedDefaultParameterRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
if let violationPosition = node.violationPosition {
violations.append(violationPosition)
}
}
}
final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
private let locationConverter: SourceLocationConverter
private let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
guard let violationPosition = node.violationPosition,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return super.visit(node)
}
correctionPositions.append(violationPosition)
let newNode = node.with(\.argumentList, [])
return super.visit(newNode)
}
}
}
private extension FunctionCallExprSyntax {
var violationPosition: AbsolutePosition? {
guard let argument = argumentList.first,
let memberExp = calledExpression.as(MemberAccessExprSyntax.self),
memberExp.name.text == "joined",
argument.label?.text == "separator",
let strLiteral = argument.expression.as(StringLiteralExprSyntax.self),
strLiteral.isEmptyString else {
return nil
}
return argument.positionAfterSkippingLeadingTrivia
}
}

View File

@ -1,12 +1,7 @@
import Foundation
import SourceKittenFramework
struct LegacyCGGeometryFunctionsRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public struct LegacyCGGeometryFunctionsRule: CorrectableRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "legacy_cggeometry_functions",
name: "Legacy CGGeometry Functions",
description: "Struct extension properties and methods are preferred over legacy functions",
@ -73,7 +68,7 @@ public struct LegacyCGGeometryFunctionsRule: CorrectableRule, ConfigurationProvi
Example("↓CGRectInset(rect, 5.0, -7.0)\n"): Example("rect.insetBy(dx: 5.0, dy: -7.0)\n"),
Example("↓CGRectOffset(rect, -2, 8.3)\n"): Example("rect.offsetBy(dx: -2, dy: 8.3)\n"),
Example("↓CGRectUnion(rect1, rect2)\n"): Example("rect1.union(rect2)\n"),
Example("↓CGRectIntersection( rect1 ,rect2)\n"): Example("rect1.intersect(rect2)\n"),
Example("↓CGRectIntersection( rect1 ,rect2)\n"): Example("rect1.intersection(rect2)\n"),
Example("↓CGRectContainsRect( rect1,rect2 )\n"): Example("rect1.contains(rect2)\n"),
Example("↓CGRectContainsPoint(rect ,point)\n"): Example("rect.contains(point)\n"),
Example("↓CGRectIntersectsRect( rect1,rect2 )\n"): Example("rect1.intersects(rect2)\n"),
@ -82,49 +77,38 @@ public struct LegacyCGGeometryFunctionsRule: CorrectableRule, ConfigurationProvi
]
)
public func validate(file: SwiftLintFile) -> [StyleViolation] {
let functions = ["CGRectGetWidth", "CGRectGetHeight", "CGRectGetMinX", "CGRectGetMidX",
"CGRectGetMaxX", "CGRectGetMinY", "CGRectGetMidY", "CGRectGetMaxY",
"CGRectIsNull", "CGRectIsEmpty", "CGRectIsInfinite", "CGRectStandardize",
"CGRectIntegral", "CGRectInset", "CGRectOffset", "CGRectUnion",
"CGRectIntersection", "CGRectContainsRect", "CGRectContainsPoint",
"CGRectIntersectsRect"]
private static let legacyFunctions: [String: LegacyFunctionRuleHelper.RewriteStrategy] = [
"CGRectGetWidth": .property(name: "width"),
"CGRectGetHeight": .property(name: "height"),
"CGRectGetMinX": .property(name: "minX"),
"CGRectGetMidX": .property(name: "midX"),
"CGRectGetMaxX": .property(name: "maxX"),
"CGRectGetMinY": .property(name: "minY"),
"CGRectGetMidY": .property(name: "midY"),
"CGRectGetMaxY": .property(name: "maxY"),
"CGRectIsNull": .property(name: "isNull"),
"CGRectIsEmpty": .property(name: "isEmpty"),
"CGRectIsInfinite": .property(name: "isInfinite"),
"CGRectStandardize": .property(name: "standardized"),
"CGRectIntegral": .property(name: "integral"),
"CGRectInset": .function(name: "insetBy", argumentLabels: ["dx", "dy"]),
"CGRectOffset": .function(name: "offsetBy", argumentLabels: ["dx", "dy"]),
"CGRectUnion": .function(name: "union", argumentLabels: [""]),
"CGRectContainsRect": .function(name: "contains", argumentLabels: [""]),
"CGRectContainsPoint": .function(name: "contains", argumentLabels: [""]),
"CGRectIntersectsRect": .function(name: "intersects", argumentLabels: [""]),
"CGRectIntersection": .function(name: "intersection", argumentLabels: [""])
]
let pattern = "\\b(" + functions.joined(separator: "|") + ")\\b"
return file.match(pattern: pattern, with: [.identifier]).map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0.location))
}
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
LegacyFunctionRuleHelper.Visitor(legacyFunctions: Self.legacyFunctions)
}
public func correct(file: SwiftLintFile) -> [Correction] {
let varName = RegexHelpers.varNameGroup
let twoVars = RegexHelpers.twoVars
let twoVariableOrNumber = RegexHelpers.twoVariableOrNumber
let patterns: [String: String] = [
"CGRectGetWidth\\(\(varName)\\)": "$1.width",
"CGRectGetHeight\\(\(varName)\\)": "$1.height",
"CGRectGetMinX\\(\(varName)\\)": "$1.minX",
"CGRectGetMidX\\(\(varName)\\)": "$1.midX",
"CGRectGetMaxX\\(\(varName)\\)": "$1.maxX",
"CGRectGetMinY\\(\(varName)\\)": "$1.minY",
"CGRectGetMidY\\(\(varName)\\)": "$1.midY",
"CGRectGetMaxY\\(\(varName)\\)": "$1.maxY",
"CGRectIsNull\\(\(varName)\\)": "$1.isNull",
"CGRectIsEmpty\\(\(varName)\\)": "$1.isEmpty",
"CGRectIsInfinite\\(\(varName)\\)": "$1.isInfinite",
"CGRectStandardize\\(\(varName)\\)": "$1.standardized",
"CGRectIntegral\\(\(varName)\\)": "$1.integral",
"CGRectInset\\(\(varName),\(twoVariableOrNumber)\\)": "$1.insetBy(dx: $2, dy: $3)",
"CGRectOffset\\(\(varName),\(twoVariableOrNumber)\\)": "$1.offsetBy(dx: $2, dy: $3)",
"CGRectUnion\\(\(twoVars)\\)": "$1.union($2)",
"CGRectIntersection\\(\(twoVars)\\)": "$1.intersect($2)",
"CGRectContainsRect\\(\(twoVars)\\)": "$1.contains($2)",
"CGRectContainsPoint\\(\(twoVars)\\)": "$1.contains($2)",
"CGRectIntersectsRect\\(\(twoVars)\\)": "$1.intersects($2)"
]
return file.correct(legacyRule: self, patterns: patterns)
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
LegacyFunctionRuleHelper.Rewriter(
legacyFunctions: Self.legacyFunctions,
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}

View File

@ -0,0 +1,98 @@
import SwiftSyntax
import SwiftSyntaxBuilder
struct LegacyConstantRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "legacy_constant",
name: "Legacy Constant",
description: "Struct-scoped constants are preferred over legacy global constants",
kind: .idiomatic,
nonTriggeringExamples: LegacyConstantRuleExamples.nonTriggeringExamples,
triggeringExamples: LegacyConstantRuleExamples.triggeringExamples,
corrections: LegacyConstantRuleExamples.corrections
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private extension LegacyConstantRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: IdentifierExprSyntax) {
if LegacyConstantRuleExamples.patterns.keys.contains(node.identifier.text) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: FunctionCallExprSyntax) {
if node.isLegacyPiExpression {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: IdentifierExprSyntax) -> ExprSyntax {
guard
let correction = LegacyConstantRuleExamples.patterns[node.identifier.text],
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
else {
return super.visit(node)
}
correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
return ("\(raw: correction)" as ExprSyntax)
.with(\.leadingTrivia, node.leadingTrivia)
.with(\.trailingTrivia, node.trailingTrivia)
}
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
guard
node.isLegacyPiExpression,
let calledExpression = node.calledExpression.as(IdentifierExprSyntax.self),
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
else {
return super.visit(node)
}
correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
return ("\(raw: calledExpression.identifier.text).pi" as ExprSyntax)
.with(\.leadingTrivia, node.leadingTrivia)
.with(\.trailingTrivia, node.trailingTrivia)
}
}
}
private extension FunctionCallExprSyntax {
var isLegacyPiExpression: Bool {
guard
let calledExpression = calledExpression.as(IdentifierExprSyntax.self),
calledExpression.identifier.text == "CGFloat" || calledExpression.identifier.text == "Float",
let argument = argumentList.onlyElement?.expression.as(IdentifierExprSyntax.self),
argument.identifier.text == "M_PI"
else {
return false
}
return true
}
}

View File

@ -48,8 +48,6 @@ internal struct LegacyConstantRuleExamples {
"NSZeroPoint": "NSPoint.zero",
"NSZeroRect": "NSRect.zero",
"NSZeroSize": "NSSize.zero",
"CGRectNull": "CGRect.null",
"CGFloat\\(M_PI\\)": "CGFloat.pi",
"Float\\(M_PI\\)": "Float.pi"
"CGRectNull": "CGRect.null"
]
}

View File

@ -1,15 +1,12 @@
import Foundation
import SourceKittenFramework
import SwiftSyntax
public struct LegacyConstructorRule: ASTRule, CorrectableRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
struct LegacyConstructorRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "legacy_constructor",
name: "Legacy Constructor",
description: "Swift constructors are preferred over legacy convenience functions.",
description: "Swift constructors are preferred over legacy convenience functions",
kind: .idiomatic,
nonTriggeringExamples: [
Example("CGPoint(x: 10, y: 10)"),
@ -62,22 +59,22 @@ public struct LegacyConstructorRule: ASTRule, CorrectableRule, ConfigurationProv
Example("↓UIOffsetMake(horizontal, vertical)")
],
corrections: [
Example("↓CGPointMake(10, 10 )\n"): Example("CGPoint(x: 10, y: 10)\n"),
Example("↓CGPointMake(xPos, yPos )\n"): Example("CGPoint(x: xPos, y: yPos)\n"),
Example("↓CGPointMake(10, 10)\n"): Example("CGPoint(x: 10, y: 10)\n"),
Example("↓CGPointMake(xPos, yPos)\n"): Example("CGPoint(x: xPos, y: yPos)\n"),
Example("↓CGSizeMake(10, 10)\n"): Example("CGSize(width: 10, height: 10)\n"),
Example("↓CGSizeMake( aWidth, aHeight )\n"): Example("CGSize(width: aWidth, height: aHeight)\n"),
Example("↓CGSizeMake( aWidth, aHeight )\n"): Example("CGSize( width: aWidth, height: aHeight )\n"),
Example("↓CGRectMake(0, 0, 10, 10)\n"): Example("CGRect(x: 0, y: 0, width: 10, height: 10)\n"),
Example("↓CGRectMake(xPos, yPos , width, height)\n"):
Example("CGRect(x: xPos, y: yPos, width: width, height: height)\n"),
Example("CGRect(x: xPos, y: yPos , width: width, height: height)\n"),
Example("↓CGVectorMake(10, 10)\n"): Example("CGVector(dx: 10, dy: 10)\n"),
Example("↓CGVectorMake(deltaX, deltaY)\n"): Example("CGVector(dx: deltaX, dy: deltaY)\n"),
Example("↓NSMakePoint(10, 10 )\n"): Example("NSPoint(x: 10, y: 10)\n"),
Example("↓NSMakePoint(xPos, yPos )\n"): Example("NSPoint(x: xPos, y: yPos)\n"),
Example("↓NSMakePoint(10, 10 )\n"): Example("NSPoint(x: 10, y: 10 )\n"),
Example("↓NSMakePoint(xPos, yPos )\n"): Example("NSPoint(x: xPos, y: yPos )\n"),
Example("↓NSMakeSize(10, 10)\n"): Example("NSSize(width: 10, height: 10)\n"),
Example("↓NSMakeSize( aWidth, aHeight )\n"): Example("NSSize(width: aWidth, height: aHeight)\n"),
Example("↓NSMakeSize( aWidth, aHeight )\n"): Example("NSSize( width: aWidth, height: aHeight )\n"),
Example("↓NSMakeRect(0, 0, 10, 10)\n"): Example("NSRect(x: 0, y: 0, width: 10, height: 10)\n"),
Example("↓NSMakeRect(xPos, yPos , width, height)\n"):
Example("NSRect(x: xPos, y: yPos, width: width, height: height)\n"),
Example("NSRect(x: xPos, y: yPos , width: width, height: height)\n"),
Example("↓NSMakeRange(10, 1)\n"): Example("NSRange(location: 10, length: 1)\n"),
Example("↓NSMakeRange(loc, len)\n"): Example("NSRange(location: loc, length: len)\n"),
Example("↓CGVectorMake(10, 10)\n↓NSMakeRange(10, 1)\n"):
@ -125,99 +122,66 @@ public struct LegacyConstructorRule: ASTRule, CorrectableRule, ConfigurationProv
"NSEdgeInsetsMake": "NSEdgeInsets",
"UIOffsetMake": "UIOffset"]
public func validate(file: SwiftLintFile, kind: SwiftExpressionKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard containsViolation(kind: kind, dictionary: dictionary),
let offset = dictionary.offset else {
return []
}
return [
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset))
]
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
private func violations(in file: SwiftLintFile, kind: SwiftExpressionKind,
dictionary: SourceKittenDictionary) -> [SourceKittenDictionary] {
guard containsViolation(kind: kind, dictionary: dictionary) else {
return []
}
return [dictionary]
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private func containsViolation(kind: SwiftExpressionKind,
dictionary: SourceKittenDictionary) -> Bool {
guard kind == .call,
let name = dictionary.name,
dictionary.offset != nil,
let expectedArguments = Self.constructorsToArguments[name],
dictionary.enclosedArguments.count == expectedArguments.count else {
return false
}
return true
}
private func violations(in file: SwiftLintFile,
dictionary: SourceKittenDictionary) -> [SourceKittenDictionary] {
return dictionary.traverseDepthFirst { subDict in
guard let kind = subDict.expressionKind else { return nil }
return violations(in: file, kind: kind, dictionary: subDict)
}
}
private func violations(in file: SwiftLintFile) -> [SourceKittenDictionary] {
return violations(in: file, dictionary: file.structureDictionary).sorted { lhs, rhs in
(lhs.offset ?? 0) < (rhs.offset ?? 0)
}
}
public func correct(file: SwiftLintFile) -> [Correction] {
let violatingDictionaries = violations(in: file)
var correctedContents = file.contents
var adjustedLocations = [Int]()
for dictionary in violatingDictionaries.reversed() {
guard let byteRange = dictionary.byteRange,
let range = file.stringView.byteRangeToNSRange(byteRange),
let name = dictionary.name,
let correctedName = Self.constructorsToCorrectedNames[name],
file.ruleEnabled(violatingRanges: [range], for: self) == [range],
case let arguments = argumentsContents(file: file, arguments: dictionary.enclosedArguments),
let expectedArguments = Self.constructorsToArguments[name],
arguments.count == expectedArguments.count else {
continue
}
if let indexRange = correctedContents.nsrangeToIndexRange(range) {
let joinedArguments = zip(expectedArguments, arguments).map { "\($0): \($1)" }.joined(separator: ", ")
let replacement = correctedName + "(" + joinedArguments + ")"
correctedContents = correctedContents.replacingCharacters(in: indexRange, with: replacement)
adjustedLocations.insert(range.location, at: 0)
private extension LegacyConstructorRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
if let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self),
constructorsToCorrectedNames[identifierExpr.identifier.text] != nil {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
let corrections = adjustedLocations.map {
Correction(ruleDescription: Self.description,
location: Location(file: file, characterOffset: $0))
}
file.write(correctedContents)
return corrections
}
private func argumentsContents(file: SwiftLintFile, arguments: [SourceKittenDictionary]) -> [String] {
let contents = file.stringView
return arguments.compactMap { argument -> String? in
guard argument.name == nil, let byteRange = argument.byteRange else {
return nil
final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
guard let identifierExpr = node.calledExpression.as(IdentifierExprSyntax.self),
case let identifier = identifierExpr.identifier.text,
let correctedName = constructorsToCorrectedNames[identifier],
let args = constructorsToArguments[identifier],
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return super.visit(node)
}
return contents.substringWithByteRange(byteRange)
correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let arguments = TupleExprElementListSyntax(node.argumentList.enumerated().map { index, elem in
elem
.with(\.label, .identifier(args[index]))
.with(\.colon, .colonToken(trailingTrivia: .space))
})
let newExpression = identifierExpr.with(
\.identifier,
.identifier(
correctedName,
leadingTrivia: identifierExpr.identifier.leadingTrivia,
trailingTrivia: identifierExpr.identifier.trailingTrivia
)
)
let newNode = node
.with(\.calledExpression, ExprSyntax(newExpression))
.with(\.argumentList, arguments)
return super.visit(newNode)
}
}
}

View File

@ -1,11 +1,9 @@
import SourceKittenFramework
import SwiftSyntax
public struct LegacyHashingRule: ASTRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
struct LegacyHashingRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "legacy_hashing",
name: "Legacy Hashing",
description: "Prefer using the `hash(into:)` function instead of overriding `hashValue`",
@ -75,21 +73,27 @@ public struct LegacyHashingRule: ASTRule, ConfigurationProviderRule {
]
)
// MARK: - ASTRule
public func validate(file: SwiftLintFile,
kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
guard kind == .varInstance,
dictionary.setterAccessibility == nil,
dictionary.typeName == "Int",
dictionary.name == "hashValue",
let offset = dictionary.offset else {
return []
}
return [StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: offset))]
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
extension LegacyHashingRule {
private final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: VariableDeclSyntax) {
guard
node.parent?.is(MemberDeclListItemSyntax.self) == true,
node.bindingKeyword.tokenKind == .keyword(.var),
let binding = node.bindings.onlyElement,
let identifier = binding.pattern.as(IdentifierPatternSyntax.self),
identifier.identifier.text == "hashValue",
let returnType = binding.typeAnnotation?.type.as(SimpleTypeIdentifierSyntax.self),
returnType.name.text == "Int"
else {
return
}
violations.append(node.bindingKeyword.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -0,0 +1,84 @@
import SwiftSyntax
struct LegacyMultipleRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "legacy_multiple",
name: "Legacy Multiple",
description: "Prefer using the `isMultiple(of:)` function instead of using the remainder operator (`%`)",
kind: .idiomatic,
nonTriggeringExamples: [
Example("cell.contentView.backgroundColor = indexPath.row.isMultiple(of: 2) ? .gray : .white"),
Example("guard count.isMultiple(of: 2) else { throw DecodingError.dataCorrupted(...) }"),
Example("sanityCheck(bytes > 0 && bytes.isMultiple(of: 4), \"capacity must be multiple of 4 bytes\")"),
Example("guard let i = reversedNumbers.firstIndex(where: { $0.isMultiple(of: 2) }) else { return }"),
Example("""
let constant = 56
let isMultiple = value.isMultiple(of: constant)
"""),
Example("""
let constant = 56
let secret = value % constant == 5
"""),
Example("let secretValue = (value % 3) + 2")
],
triggeringExamples: [
Example("cell.contentView.backgroundColor = indexPath.row ↓% 2 == 0 ? .gray : .white"),
Example("cell.contentView.backgroundColor = 0 == indexPath.row ↓% 2 ? .gray : .white"),
Example("cell.contentView.backgroundColor = indexPath.row ↓% 2 != 0 ? .gray : .white"),
Example("guard count ↓% 2 == 0 else { throw DecodingError.dataCorrupted(...) }"),
Example("sanityCheck(bytes > 0 && bytes ↓% 4 == 0, \"capacity must be multiple of 4 bytes\")"),
Example("guard let i = reversedNumbers.firstIndex(where: { $0 ↓% 2 == 0 }) else { return }"),
Example("""
let constant = 56
let isMultiple = value % constant == 0
""")
]
)
func preprocess(file: SwiftLintFile) -> SourceFileSyntax? {
file.foldedSyntaxTree
}
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension LegacyMultipleRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: InfixOperatorExprSyntax) {
guard let operatorNode = node.operatorOperand.as(BinaryOperatorExprSyntax.self),
operatorNode.operatorToken.tokenKind == .binaryOperator("%"),
let parent = node.parent?.as(InfixOperatorExprSyntax.self),
let parentOperatorNode = parent.operatorOperand.as(BinaryOperatorExprSyntax.self),
parentOperatorNode.isEqualityOrInequalityOperator else {
return
}
let isExprEqualTo0 = {
parent.leftOperand.as(InfixOperatorExprSyntax.self) == node &&
parent.rightOperand.as(IntegerLiteralExprSyntax.self)?.isZero == true
}
let is0EqualToExpr = {
parent.leftOperand.as(IntegerLiteralExprSyntax.self)?.isZero == true &&
parent.rightOperand.as(InfixOperatorExprSyntax.self) == node
}
guard isExprEqualTo0() || is0EqualToExpr() else {
return
}
violations.append(node.operatorOperand.positionAfterSkippingLeadingTrivia)
}
}
}
private extension BinaryOperatorExprSyntax {
var isEqualityOrInequalityOperator: Bool {
operatorToken.tokenKind == .binaryOperator("==") ||
operatorToken.tokenKind == .binaryOperator("!=")
}
}

View File

@ -1,12 +1,7 @@
import Foundation
import SourceKittenFramework
struct LegacyNSGeometryFunctionsRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public struct LegacyNSGeometryFunctionsRule: CorrectableRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "legacy_nsgeometry_functions",
name: "Legacy NSGeometry Functions",
description: "Struct extension properties and methods are preferred over legacy functions",
@ -25,7 +20,7 @@ public struct LegacyNSGeometryFunctionsRule: CorrectableRule, ConfigurationProvi
Example("rect.insetBy(dx: 5.0, dy: -7.0)"),
Example("rect.offsetBy(dx: 5.0, dy: -7.0)"),
Example("rect1.union(rect2)"),
Example("rect1.intersect(rect2)"),
Example("rect1.intersection(rect2)"),
// "rect.divide(atDistance: 10.2, fromEdge: edge)", No correction available for divide
Example("rect1.contains(rect2)"),
Example("rect.contains(point)"),
@ -72,59 +67,48 @@ public struct LegacyNSGeometryFunctionsRule: CorrectableRule, ConfigurationProvi
Example("↓NSInsetRect(rect, 5.0, -7.0)\n"): Example("rect.insetBy(dx: 5.0, dy: -7.0)\n"),
Example("↓NSOffsetRect(rect, -2, 8.3)\n"): Example("rect.offsetBy(dx: -2, dy: 8.3)\n"),
Example("↓NSUnionRect(rect1, rect2)\n"): Example("rect1.union(rect2)\n"),
Example("↓NSIntersectionRect( rect1 ,rect2)\n"): Example("rect1.intersect(rect2)\n"),
Example("↓NSContainsRect( rect1,rect2 )\n"): Example("rect1.contains(rect2)\n"),
Example("↓NSPointInRect(point ,rect)\n"): Example("rect.contains(point)\n"), // note order of arguments
Example("↓NSIntersectsRect( rect1,rect2 )\n"): Example("rect1.intersects(rect2)\n"),
Example("↓NSIntersectsRect(rect1, rect2 )\n↓NSWidth(rect )\n"):
Example("rect1.intersects(rect2)\nrect.width\n")
Example("rect1.intersects(rect2)\nrect.width\n"),
Example("↓NSIntersectionRect(rect1, rect2)"): Example("rect1.intersection(rect2)")
]
)
public func validate(file: SwiftLintFile) -> [StyleViolation] {
let functions = ["NSWidth", "NSHeight", "NSMinX", "NSMidX",
"NSMaxX", "NSMinY", "NSMidY", "NSMaxY",
"NSEqualRects", "NSEqualSizes", "NSEqualPoints", "NSEdgeInsetsEqual",
"NSIsEmptyRect", "NSIntegralRect", "NSInsetRect",
"NSOffsetRect", "NSUnionRect", "NSIntersectionRect",
"NSContainsRect", "NSPointInRect", "NSIntersectsRect"]
private static let legacyFunctions: [String: LegacyFunctionRuleHelper.RewriteStrategy] = [
"NSHeight": .property(name: "height"),
"NSIntegralRect": .property(name: "integral"),
"NSIsEmptyRect": .property(name: "isEmpty"),
"NSMaxX": .property(name: "maxX"),
"NSMaxY": .property(name: "maxY"),
"NSMidX": .property(name: "midX"),
"NSMidY": .property(name: "midY"),
"NSMinX": .property(name: "minX"),
"NSMinY": .property(name: "minY"),
"NSWidth": .property(name: "width"),
"NSEqualPoints": .equal,
"NSEqualSizes": .equal,
"NSEqualRects": .equal,
"NSEdgeInsetsEqual": .equal,
"NSInsetRect": .function(name: "insetBy", argumentLabels: ["dx", "dy"]),
"NSOffsetRect": .function(name: "offsetBy", argumentLabels: ["dx", "dy"]),
"NSUnionRect": .function(name: "union", argumentLabels: [""]),
"NSContainsRect": .function(name: "contains", argumentLabels: [""]),
"NSIntersectsRect": .function(name: "intersects", argumentLabels: [""]),
"NSIntersectionRect": .function(name: "intersection", argumentLabels: [""]),
"NSPointInRect": .function(name: "contains", argumentLabels: [""], reversed: true)
]
let pattern = "\\b(" + functions.joined(separator: "|") + ")\\b"
return file.match(pattern: pattern, with: [.identifier]).map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, characterOffset: $0.location))
}
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
LegacyFunctionRuleHelper.Visitor(legacyFunctions: Self.legacyFunctions)
}
public func correct(file: SwiftLintFile) -> [Correction] {
let varName = RegexHelpers.varNameGroup
let twoVars = RegexHelpers.twoVars
let twoVariableOrNumber = RegexHelpers.twoVariableOrNumber
let patterns: [String: String] = [
"NSWidth\\(\(varName)\\)": "$1.width",
"NSHeight\\(\(varName)\\)": "$1.height",
"NSMinX\\(\(varName)\\)": "$1.minX",
"NSMidX\\(\(varName)\\)": "$1.midX",
"NSMaxX\\(\(varName)\\)": "$1.maxX",
"NSMinY\\(\(varName)\\)": "$1.minY",
"NSMidY\\(\(varName)\\)": "$1.midY",
"NSMaxY\\(\(varName)\\)": "$1.maxY",
"NSEqualRects\\(\(twoVars)\\)": "$1 == $2",
"NSEqualSizes\\(\(twoVars)\\)": "$1 == $2",
"NSEqualPoints\\(\(twoVars)\\)": "$1 == $2",
"NSEdgeInsetsEqual\\(\(twoVars)\\)": "$1 == $2",
"NSIsEmptyRect\\(\(varName)\\)": "$1.isEmpty",
"NSIntegralRect\\(\(varName)\\)": "$1.integral",
"NSInsetRect\\(\(varName),\(twoVariableOrNumber)\\)": "$1.insetBy(dx: $2, dy: $3)",
"NSOffsetRect\\(\(varName),\(twoVariableOrNumber)\\)": "$1.offsetBy(dx: $2, dy: $3)",
"NSUnionRect\\(\(twoVars)\\)": "$1.union($2)",
"NSIntersectionRect\\(\(twoVars)\\)": "$1.intersect($2)",
"NSContainsRect\\(\(twoVars)\\)": "$1.contains($2)",
"NSPointInRect\\(\(twoVars)\\)": "$2.contains($1)", // note order of arguments
"NSIntersectsRect\\(\(twoVars)\\)": "$1.intersects($2)"
]
return file.correct(legacyRule: self, patterns: patterns)
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
LegacyFunctionRuleHelper.Rewriter(
legacyFunctions: Self.legacyFunctions,
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}

View File

@ -0,0 +1,98 @@
import SwiftSyntax
private let legacyObjcTypes = [
"NSAffineTransform",
"NSArray",
"NSCalendar",
"NSCharacterSet",
"NSData",
"NSDateComponents",
"NSDateInterval",
"NSDate",
"NSDecimalNumber",
"NSDictionary",
"NSIndexPath",
"NSIndexSet",
"NSLocale",
"NSMeasurement",
"NSNotification",
"NSNumber",
"NSPersonNameComponents",
"NSSet",
"NSString",
"NSTimeZone",
"NSURL",
"NSURLComponents",
"NSURLQueryItem",
"NSURLRequest",
"NSUUID"
]
struct LegacyObjcTypeRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "legacy_objc_type",
name: "Legacy Objective-C Reference Type",
description: "Prefer Swift value types to bridged Objective-C reference types",
kind: .idiomatic,
nonTriggeringExamples: [
Example("var array = Array<Int>()\n"),
Example("var calendar: Calendar? = nil"),
Example("var formatter: NSDataDetector"),
Example("var className: String = NSStringFromClass(MyClass.self)"),
Example("_ = URLRequest.CachePolicy.reloadIgnoringLocalCacheData"),
Example(#"_ = Notification.Name("com.apple.Music.playerInfo")"#)
],
triggeringExamples: [
Example("var array = ↓NSArray()"),
Example("var calendar: ↓NSCalendar? = nil"),
Example("_ = ↓NSURLRequest.CachePolicy.reloadIgnoringLocalCacheData"),
Example(#"_ = ↓NSNotification.Name("com.apple.Music.playerInfo")"#),
Example(#"""
let keyValuePair: (Int) -> (NSString, NSString) = {
let n = "\($0)" as NSString; return (n, n)
}
dictionary = [NSString: NSString](uniqueKeysWithValues:
(1...10_000).lazy.map(keyValuePair))
"""#),
Example("""
extension Foundation.Notification.Name {
static var reachabilityChanged: Foundation.NSNotification.Name {
return Foundation.Notification.Name("org.wordpress.reachability.changed")
}
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension LegacyObjcTypeRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: SimpleTypeIdentifierSyntax) {
if let typeName = node.typeName, legacyObjcTypes.contains(typeName) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: IdentifierExprSyntax) {
if legacyObjcTypes.contains(node.identifier.text) {
violations.append(node.identifier.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: MemberTypeIdentifierSyntax) {
guard node.baseType.as(SimpleTypeIdentifierSyntax.self)?.typeName == "Foundation",
legacyObjcTypes.contains(node.name.text)
else {
return
}
violations.append(node.name.positionAfterSkippingLeadingTrivia)
}
}
}

View File

@ -0,0 +1,43 @@
import SwiftSyntax
struct LegacyRandomRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static var description = RuleDescription(
identifier: "legacy_random",
name: "Legacy Random",
description: "Prefer using `type.random(in:)` over legacy functions",
kind: .idiomatic,
nonTriggeringExamples: [
Example("Int.random(in: 0..<10)\n"),
Example("Double.random(in: 8.6...111.34)\n"),
Example("Float.random(in: 0 ..< 1)\n")
],
triggeringExamples: [
Example("↓arc4random()\n"),
Example("↓arc4random_uniform(83)\n"),
Example("↓drand48()\n")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension LegacyRandomRule {
final class Visitor: ViolationsSyntaxVisitor {
private static let legacyRandomFunctions: Set<String> = [
"arc4random",
"arc4random_uniform",
"drand48"
]
override func visitPost(_ node: FunctionCallExprSyntax) {
if let function = node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text,
Self.legacyRandomFunctions.contains(function) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
}

View File

@ -1,15 +1,13 @@
import Foundation
import SourceKittenFramework
public struct NimbleOperatorRule: ConfigurationProviderRule, OptInRule, CorrectableRule {
public var configuration = SeverityConfiguration(.warning)
struct NimbleOperatorRule: ConfigurationProviderRule, OptInRule, CorrectableRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "nimble_operator",
name: "Nimble Operator",
description: "Prefer Nimble operator overloads over free matcher functions.",
description: "Prefer Nimble operator overloads over free matcher functions",
kind: .idiomatic,
nonTriggeringExamples: [
Example("expect(seagull.squawk) != \"Hi!\"\n"),
@ -98,7 +96,7 @@ public struct NimbleOperatorRule: ConfigurationProviderRule, OptInRule, Correcta
"beNil": (to: "==", toNot: "!=", .nullary(analogueValue: "nil"))
]
public func validate(file: SwiftLintFile) -> [StyleViolation] {
func validate(file: SwiftLintFile) -> [StyleViolation] {
let matches = violationMatchesRanges(in: file)
return matches.map {
StyleViolation(ruleDescription: Self.description,
@ -114,12 +112,10 @@ public struct NimbleOperatorRule: ConfigurationProviderRule, OptInRule, Correcta
return false
}
let containsCall = file.structureDictionary.structures(forByteOffset: byteRange.upperBound - 1)
return file.structureDictionary.structures(forByteOffset: byteRange.upperBound - 1)
.contains(where: { dict -> Bool in
return dict.expressionKind == .call && (dict.name ?? "").starts(with: "expect")
})
return containsCall
}
}
@ -144,7 +140,7 @@ public struct NimbleOperatorRule: ConfigurationProviderRule, OptInRule, Correcta
.map { $0.0 }
}
public func correct(file: SwiftLintFile) -> [Correction] {
func correct(file: SwiftLintFile) -> [Correction] {
let matches = violationMatchesRanges(in: file)
.filter { file.ruleEnabled(violatingRanges: [$0], for: self).isNotEmpty }
guard matches.isNotEmpty else { return [] }

View File

@ -0,0 +1,39 @@
import SwiftSyntax
struct NoExtensionAccessModifierRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.error)
static let description = RuleDescription(
identifier: "no_extension_access_modifier",
name: "No Extension Access Modifier",
description: "Prefer not to use extension access modifiers",
kind: .idiomatic,
nonTriggeringExamples: [
Example("extension String {}"),
Example("\n\n extension String {}")
],
triggeringExamples: [
Example("↓private extension String {}"),
Example("↓public \n extension String {}"),
Example("↓open extension String {}"),
Example("↓internal extension String {}"),
Example("↓fileprivate extension String {}")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension NoExtensionAccessModifierRule {
final class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .all }
override func visitPost(_ node: ExtensionDeclSyntax) {
if let modifiers = node.modifiers, modifiers.isNotEmpty {
violations.append(modifiers.positionAfterSkippingLeadingTrivia)
}
}
}
}

View File

@ -0,0 +1,42 @@
import SwiftSyntax
struct NoFallthroughOnlyRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "no_fallthrough_only",
name: "No Fallthrough only",
description: "Fallthroughs can only be used if the `case` contains at least one other statement",
kind: .idiomatic,
nonTriggeringExamples: NoFallthroughOnlyRuleExamples.nonTriggeringExamples,
triggeringExamples: NoFallthroughOnlyRuleExamples.triggeringExamples
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension NoFallthroughOnlyRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: SwitchCaseListSyntax) {
let cases = node.compactMap { $0.as(SwitchCaseSyntax.self) }
let localViolations = cases.enumerated()
.compactMap { index, element -> AbsolutePosition? in
if let fallthroughStmt = element.statements.onlyElement?.item.as(FallthroughStmtSyntax.self) {
if case let nextCaseIndex = cases.index(after: index),
nextCaseIndex < cases.endIndex,
case let nextCase = cases[nextCaseIndex],
nextCase.unknownAttr != nil {
return nil
}
return fallthroughStmt.positionAfterSkippingLeadingTrivia
}
return nil
}
violations.append(contentsOf: localViolations)
}
}
}

View File

@ -1,14 +1,12 @@
import SourceKittenFramework
public struct NoGroupingExtensionRule: OptInRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
struct NoGroupingExtensionRule: OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "no_grouping_extension",
name: "No Grouping Extension",
description: "Extensions shouldn't be used to group code within the same source file.",
description: "Extensions shouldn't be used to group code within the same source file",
kind: .idiomatic,
nonTriggeringExamples: [
Example("protocol Food {}\nextension Food {}\n"),
@ -23,7 +21,7 @@ public struct NoGroupingExtensionRule: OptInRule, ConfigurationProviderRule {
]
)
public func validate(file: SwiftLintFile) -> [StyleViolation] {
func validate(file: SwiftLintFile) -> [StyleViolation] {
let collector = NamespaceCollector(dictionary: file.structureDictionary)
let elements = collector.findAllElements(of: [.class, .enum, .struct, .extension])

View File

@ -0,0 +1,129 @@
import SwiftSyntax
struct NoMagicNumbersRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = NoMagicNumbersConfiguration()
static let description = RuleDescription(
identifier: "no_magic_numbers",
name: "No Magic Numbers",
description: "Magic numbers should be replaced by named constants",
kind: .idiomatic,
nonTriggeringExamples: [
Example("var foo = 123"),
Example("static let bar: Double = 0.123"),
Example("let a = b + 1.0"),
Example("array[0] + array[1] "),
Example("let foo = 1_000.000_01"),
Example("// array[1337]"),
Example("baz(\"9999\")"),
Example("""
func foo() {
let x: Int = 2
let y = 3
let vector = [x, y, -1]
}
"""),
Example("""
class A {
var foo: Double = 132
static let bar: Double = 0.98
}
"""),
Example("""
@available(iOS 13, *)
func version() {
if #available(iOS 13, OSX 10.10, *) {
return
}
}
"""),
Example("""
enum Example: Int {
case positive = 2
case negative = -2
}
"""),
Example("""
class FooTests: XCTestCase {
let array: [Int] = []
let bar = array[42]
}
"""),
Example("""
class FooTests: XCTestCase {
class Bar {
let array: [Int] = []
let bar = array[42]
}
}
""")
],
triggeringExamples: [
Example("foo(↓321)"),
Example("bar(↓1_000.005_01)"),
Example("array[↓42]"),
Example("let box = array[↓12 + ↓14]"),
Example("let a = b + ↓2.0"),
Example("Color.primary.opacity(isAnimate ? ↓0.1 : ↓1.5)")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate, testParentClasses: configuration.testParentClasses)
}
}
private extension NoMagicNumbersRule {
final class Visitor: ViolationsSyntaxVisitor {
private let testParentClasses: Set<String>
init(viewMode: SyntaxTreeViewMode, testParentClasses: Set<String>) {
self.testParentClasses = testParentClasses
super.init(viewMode: viewMode)
}
override func visitPost(_ node: FloatLiteralExprSyntax) {
if node.isMemberOfATestClass(testParentClasses) == false, node.floatingDigits.isMagicNumber {
violations.append(node.floatingDigits.positionAfterSkippingLeadingTrivia)
}
}
override func visitPost(_ node: IntegerLiteralExprSyntax) {
if node.isMemberOfATestClass(testParentClasses) == false, node.digits.isMagicNumber {
violations.append(node.digits.positionAfterSkippingLeadingTrivia)
}
}
}
}
private extension TokenSyntax {
var isMagicNumber: Bool {
guard let number = Double(text.replacingOccurrences(of: "_", with: "")) else {
return false
}
if [0, 1].contains(number) {
return false
}
guard let grandparent = parent?.parent else {
return true
}
return !grandparent.is(InitializerClauseSyntax.self)
&& grandparent.as(PrefixOperatorExprSyntax.self)?.parent?.is(InitializerClauseSyntax.self) != true
}
}
private extension ExprSyntaxProtocol {
func isMemberOfATestClass(_ testParentClasses: Set<String>) -> Bool {
var parent = parent
while parent != nil {
if
let classDecl = parent?.as(ClassDeclSyntax.self),
classDecl.isXCTestCase(testParentClasses)
{
return true
}
parent = parent?.parent
}
return false
}
}

View File

@ -0,0 +1,117 @@
import SwiftSyntax
struct ObjectLiteralRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = ObjectLiteralConfiguration<Self>()
static let description = RuleDescription(
identifier: "object_literal",
name: "Object Literal",
description: "Prefer object literals over image and color inits",
kind: .idiomatic,
nonTriggeringExamples: [
Example("let image = #imageLiteral(resourceName: \"image.jpg\")"),
Example("let color = #colorLiteral(red: 0.9607843161, green: 0.7058823705, blue: 0.200000003, alpha: 1)"),
Example("let image = UIImage(named: aVariable)"),
Example("let image = UIImage(named: \"interpolated \\(variable)\")"),
Example("let color = UIColor(red: value, green: value, blue: value, alpha: 1)"),
Example("let image = NSImage(named: aVariable)"),
Example("let image = NSImage(named: \"interpolated \\(variable)\")"),
Example("let color = NSColor(red: value, green: value, blue: value, alpha: 1)")
],
triggeringExamples: ["", ".init"].flatMap { (method: String) -> [Example] in
["UI", "NS"].flatMap { (prefix: String) -> [Example] in
[
Example("let image = ↓\(prefix)Image\(method)(named: \"foo\")"),
Example("let color = ↓\(prefix)Color\(method)(red: 0.3, green: 0.3, blue: 0.3, alpha: 1)"),
// swiftlint:disable:next line_length
Example("let color = ↓\(prefix)Color\(method)(red: 100 / 255.0, green: 50 / 255.0, blue: 0, alpha: 1)"),
Example("let color = ↓\(prefix)Color\(method)(white: 0.5, alpha: 1)")
]
}
}
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(validateImageLiteral: configuration.imageLiteral, validateColorLiteral: configuration.colorLiteral)
}
}
private extension ObjectLiteralRule {
final class Visitor: ViolationsSyntaxVisitor {
private let validateImageLiteral: Bool
private let validateColorLiteral: Bool
init(validateImageLiteral: Bool, validateColorLiteral: Bool) {
self.validateImageLiteral = validateImageLiteral
self.validateColorLiteral = validateColorLiteral
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: FunctionCallExprSyntax) {
guard validateColorLiteral || validateImageLiteral else {
return
}
let name = node.calledExpression.trimmedDescription
if validateImageLiteral, isImageNamedInit(node: node, name: name) {
violations.append(node.positionAfterSkippingLeadingTrivia)
} else if validateColorLiteral, isColorInit(node: node, name: name) {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
private func isImageNamedInit(node: FunctionCallExprSyntax, name: String) -> Bool {
guard inits(forClasses: ["UIImage", "NSImage"]).contains(name),
node.argumentList.compactMap(\.label?.text) == ["named"],
let argument = node.argumentList.first?.expression.as(StringLiteralExprSyntax.self),
argument.isConstantString else {
return false
}
return true
}
private func isColorInit(node: FunctionCallExprSyntax, name: String) -> Bool {
guard inits(forClasses: ["UIColor", "NSColor"]).contains(name),
case let argumentsNames = node.argumentList.compactMap(\.label?.text),
argumentsNames == ["red", "green", "blue", "alpha"] || argumentsNames == ["white", "alpha"] else {
return false
}
return node.argumentList.allSatisfy { elem in
elem.expression.canBeExpressedAsColorLiteralParams
}
}
private func inits(forClasses names: [String]) -> [String] {
return names.flatMap { name in
[
name,
name + ".init"
]
}
}
}
}
private extension StringLiteralExprSyntax {
var isConstantString: Bool {
segments.allSatisfy { $0.is(StringSegmentSyntax.self) }
}
}
private extension ExprSyntax {
var canBeExpressedAsColorLiteralParams: Bool {
if self.is(FloatLiteralExprSyntax.self) ||
self.is(IntegerLiteralExprSyntax.self) ||
self.is(BinaryOperatorExprSyntax.self) {
return true
}
if let expr = self.as(SequenceExprSyntax.self) {
return expr.elements.allSatisfy(\.canBeExpressedAsColorLiteralParams)
}
return false
}
}

View File

@ -0,0 +1,106 @@
import SwiftSyntax
struct PatternMatchingKeywordsRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "pattern_matching_keywords",
name: "Pattern Matching Keywords",
description: "Combine multiple pattern matching bindings by moving keywords out of tuples",
kind: .idiomatic,
nonTriggeringExamples: [
Example("default"),
Example("case 1"),
Example("case bar"),
Example("case let (x, y)"),
Example("case .foo(let x)"),
Example("case let .foo(x, y)"),
Example("case .foo(let x), .bar(let x)"),
Example("case .foo(let x, var y)"),
Example("case var (x, y)"),
Example("case .foo(var x)"),
Example("case var .foo(x, y)")
].map(wrapInSwitch),
triggeringExamples: [
Example("case (↓let x, ↓let y)"),
Example("case (↓let x, ↓let y, .foo)"),
Example("case (↓let x, ↓let y, _)"),
Example("case .foo(↓let x, ↓let y)"),
Example("case (.yamlParsing(↓let x), .yamlParsing(↓let y))"),
Example("case (↓var x, ↓var y)"),
Example("case .foo(↓var x, ↓var y)"),
Example("case (.yamlParsing(↓var x), .yamlParsing(↓var y))")
].map(wrapInSwitch)
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension PatternMatchingKeywordsRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: CaseItemSyntax) {
let localViolations = TupleVisitor(viewMode: .sourceAccurate)
.walk(tree: node.pattern, handler: \.violations)
violations.append(contentsOf: localViolations)
}
}
final class TupleVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: TupleExprElementListSyntax) {
let list = node.flatteningEnumPatterns()
.compactMap { elem in
elem.expression.asValueBindingPattern()
}
guard list.count > 1,
let firstLetOrVar = list.first?.bindingKeyword.tokenKind else {
return
}
let hasViolation = list.allSatisfy { elem in
elem.bindingKeyword.tokenKind == firstLetOrVar
}
guard hasViolation else {
return
}
violations.append(contentsOf: list.compactMap { elem in
return elem.bindingKeyword.positionAfterSkippingLeadingTrivia
})
}
}
}
private extension TupleExprElementListSyntax {
func flatteningEnumPatterns() -> [TupleExprElementSyntax] {
flatMap { elem in
guard let pattern = elem.expression.as(FunctionCallExprSyntax.self),
pattern.calledExpression.is(MemberAccessExprSyntax.self) else {
return [elem]
}
return Array(pattern.argumentList)
}
}
}
private extension ExprSyntax {
func asValueBindingPattern() -> ValueBindingPatternSyntax? {
if let pattern = self.as(UnresolvedPatternExprSyntax.self) {
return pattern.pattern.as(ValueBindingPatternSyntax.self)
}
return nil
}
}
private func wrapInSwitch(_ example: Example) -> Example {
return example.with(code: """
switch foo {
\(example.code): break
}
""")
}

View File

@ -0,0 +1,39 @@
import SwiftSyntax
struct PreferNimbleRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "prefer_nimble",
name: "Prefer Nimble",
description: "Prefer Nimble matchers over XCTAssert functions",
kind: .idiomatic,
nonTriggeringExamples: [
Example("expect(foo) == 1"),
Example("expect(foo).to(equal(1))")
],
triggeringExamples: [
Example("↓XCTAssertTrue(foo)"),
Example("↓XCTAssertEqual(foo, 2)"),
Example("↓XCTAssertNotEqual(foo, 2)"),
Example("↓XCTAssertNil(foo)"),
Example("↓XCTAssert(foo)"),
Example("↓XCTAssertGreaterThan(foo, 10)")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension PreferNimbleRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
if let expr = node.calledExpression.as(IdentifierExprSyntax.self),
expr.identifier.text.starts(with: "XCTAssert") {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
}

View File

@ -0,0 +1,150 @@
import SwiftSyntax
struct PreferZeroOverExplicitInitRule: SwiftSyntaxCorrectableRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "prefer_zero_over_explicit_init",
name: "Prefer Zero Over Explicit Init",
description: "Prefer `.zero` over explicit init with zero parameters (e.g. `CGPoint(x: 0, y: 0)`)",
kind: .idiomatic,
nonTriggeringExamples: [
Example("CGRect(x: 0, y: 0, width: 0, height: 1)"),
Example("CGPoint(x: 0, y: -1)"),
Example("CGSize(width: 2, height: 4)"),
Example("CGVector(dx: -5, dy: 0)"),
Example("UIEdgeInsets(top: 0, left: 1, bottom: 0, right: 1)")
],
triggeringExamples: [
Example("↓CGPoint(x: 0, y: 0)"),
Example("↓CGPoint(x: 0.000000, y: 0)"),
Example("↓CGPoint(x: 0.000000, y: 0.000)"),
Example("↓CGRect(x: 0, y: 0, width: 0, height: 0)"),
Example("↓CGSize(width: 0, height: 0)"),
Example("↓CGVector(dx: 0, dy: 0)"),
Example("↓UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)")
],
corrections: [
Example("↓CGPoint(x: 0, y: 0)"): Example("CGPoint.zero"),
Example("(↓CGPoint(x: 0, y: 0))"): Example("(CGPoint.zero)"),
Example("↓CGRect(x: 0, y: 0, width: 0, height: 0)"): Example("CGRect.zero"),
Example("↓CGSize(width: 0, height: 0.000)"): Example("CGSize.zero"),
Example("↓CGVector(dx: 0, dy: 0)"): Example("CGVector.zero"),
Example("↓UIEdgeInsets(top: 0, left: 0, bottom: 0, right: 0)"): Example("UIEdgeInsets.zero")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private extension PreferZeroOverExplicitInitRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
if node.hasViolation {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}
private final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: FunctionCallExprSyntax) -> ExprSyntax {
guard node.hasViolation,
let name = node.name,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return super.visit(node)
}
correctionPositions.append(node.positionAfterSkippingLeadingTrivia)
let newNode = MemberAccessExprSyntax(name: "zero")
.with(\.base, "\(raw: name)")
return super.visit(
newNode
.with(\.leadingTrivia, node.leadingTrivia)
.with(\.trailingTrivia, node.trailingTrivia)
)
}
}
}
private extension FunctionCallExprSyntax {
var hasViolation: Bool {
isCGPointZeroCall ||
isCGSizeCall ||
isCGRectCall ||
isCGVectorCall ||
isUIEdgeInsetsCall
}
var isCGPointZeroCall: Bool {
return name == "CGPoint" &&
argumentNames == ["x", "y"] &&
argumentsAreAllZero
}
var isCGSizeCall: Bool {
return name == "CGSize" &&
argumentNames == ["width", "height"] &&
argumentsAreAllZero
}
var isCGRectCall: Bool {
return name == "CGRect" &&
argumentNames == ["x", "y", "width", "height"] &&
argumentsAreAllZero
}
var isCGVectorCall: Bool {
return name == "CGVector" &&
argumentNames == ["dx", "dy"] &&
argumentsAreAllZero
}
var isUIEdgeInsetsCall: Bool {
return name == "UIEdgeInsets" &&
argumentNames == ["top", "left", "bottom", "right"] &&
argumentsAreAllZero
}
var name: String? {
guard let expr = calledExpression.as(IdentifierExprSyntax.self) else {
return nil
}
return expr.identifier.text
}
var argumentNames: [String?] {
argumentList.map(\.label?.text)
}
var argumentsAreAllZero: Bool {
argumentList.allSatisfy { arg in
if let intExpr = arg.expression.as(IntegerLiteralExprSyntax.self) {
return intExpr.isZero
} else if let floatExpr = arg.expression.as(FloatLiteralExprSyntax.self) {
return floatExpr.isZero
} else {
return false
}
}
}
}

View File

@ -0,0 +1,272 @@
import SwiftSyntax
struct PrivateOverFilePrivateRule: ConfigurationProviderRule, SwiftSyntaxCorrectableRule {
var configuration = PrivateOverFilePrivateConfiguration()
static let description = RuleDescription(
identifier: "private_over_fileprivate",
name: "Private over Fileprivate",
description: "Prefer `private` over `fileprivate` declarations",
kind: .idiomatic,
nonTriggeringExamples: [
Example("extension String {}"),
Example("private extension String {}"),
Example("public \n enum MyEnum {}"),
Example("open extension \n String {}"),
Example("internal extension String {}"),
Example("""
extension String {
fileprivate func Something(){}
}
"""),
Example("""
class MyClass {
fileprivate let myInt = 4
}
"""),
Example("""
class MyClass {
fileprivate(set) var myInt = 4
}
"""),
Example("""
struct Outter {
struct Inter {
fileprivate struct Inner {}
}
}
""")
],
triggeringExamples: [
Example("↓fileprivate enum MyEnum {}"),
Example("""
fileprivate class MyClass {
fileprivate(set) var myInt = 4
}
""")
],
corrections: [
Example("↓fileprivate enum MyEnum {}"): Example("private enum MyEnum {}"),
Example("↓fileprivate enum MyEnum { fileprivate class A {} }"):
Example("private enum MyEnum { fileprivate class A {} }"),
Example("↓fileprivate class MyClass {\nfileprivate(set) var myInt = 4\n}"):
Example("private class MyClass {\nfileprivate(set) var myInt = 4\n}")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(validateExtensions: configuration.validateExtensions)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
validateExtensions: configuration.validateExtensions,
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private extension PrivateOverFilePrivateRule {
final class Visitor: ViolationsSyntaxVisitor {
private let validateExtensions: Bool
init(validateExtensions: Bool) {
self.validateExtensions = validateExtensions
super.init(viewMode: .sourceAccurate)
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
if let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
if validateExtensions, let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
if let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
if let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
if let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
if let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
if let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind {
if let privateModifier = node.modifiers.fileprivateModifier {
violations.append(privateModifier.positionAfterSkippingLeadingTrivia)
}
return .skipChildren
}
}
private final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
private let validateExtensions: Bool
private let locationConverter: SourceLocationConverter
private let disabledRegions: [SourceRange]
init(validateExtensions: Bool, locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.validateExtensions = validateExtensions
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
// don't call super in any of the `visit` methods to avoid digging into the children
override func visit(_ node: ExtensionDeclSyntax) -> DeclSyntax {
guard validateExtensions, let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
override func visit(_ node: ClassDeclSyntax) -> DeclSyntax {
guard let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
override func visit(_ node: StructDeclSyntax) -> DeclSyntax {
guard let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
override func visit(_ node: EnumDeclSyntax) -> DeclSyntax {
guard let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
override func visit(_ node: ProtocolDeclSyntax) -> DeclSyntax {
guard let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
override func visit(_ node: FunctionDeclSyntax) -> DeclSyntax {
guard let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
guard let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
override func visit(_ node: TypealiasDeclSyntax) -> DeclSyntax {
guard let modifier = node.modifiers.fileprivateModifier,
let modifierIndex = node.modifiers.fileprivateModifierIndex,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter) else {
return DeclSyntax(node)
}
correctionPositions.append(modifier.positionAfterSkippingLeadingTrivia)
let newNode = node.with(\.modifiers, node.modifiers?.replacing(fileprivateModifierIndex: modifierIndex))
return DeclSyntax(newNode)
}
}
}
private extension ModifierListSyntax? {
var fileprivateModifierIndex: ModifierListSyntax.Index? {
self?.firstIndex(where: { $0.name.tokenKind == .keyword(.fileprivate) })
}
var fileprivateModifier: DeclModifierSyntax? {
fileprivateModifierIndex.flatMap { self?[$0] }
}
}
private extension ModifierListSyntax {
func replacing(fileprivateModifierIndex: ModifierListSyntax.Index) -> ModifierListSyntax? {
let fileprivateModifier = self[fileprivateModifierIndex]
return replacing(
childAt: self.distance(from: self.startIndex, to: fileprivateModifierIndex),
with: fileprivateModifier.with(
\.name,
.keyword(
.private,
leadingTrivia: fileprivateModifier.leadingTrivia,
trailingTrivia: fileprivateModifier.trailingTrivia
)
)
)
}
}

View File

@ -0,0 +1,79 @@
import SwiftSyntax
struct RedundantNilCoalescingRule: OptInRule, SwiftSyntaxCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "redundant_nil_coalescing",
name: "Redundant Nil Coalescing",
description: "nil coalescing operator is only evaluated if the lhs is nil" +
", coalescing operator with nil as rhs is redundant",
kind: .idiomatic,
nonTriggeringExamples: [
Example("var myVar: Int?; myVar ?? 0\n")
],
triggeringExamples: [
Example("var myVar: Int? = nil; myVar ↓?? nil\n")
],
corrections: [
Example("var myVar: Int? = nil; let foo = myVar↓ ?? nil\n"):
Example("var myVar: Int? = nil; let foo = myVar\n")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private extension RedundantNilCoalescingRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: TokenSyntax) {
if node.tokenKind.isNilCoalescingOperator,
node.nextToken(viewMode: .sourceAccurate)?.tokenKind == .keyword(.nil) {
violations.append(node.position)
}
}
}
private final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: ExprListSyntax) -> ExprListSyntax {
guard
node.count > 2,
let lastExpression = node.last,
lastExpression.is(NilLiteralExprSyntax.self),
let secondToLastExpression = node.dropLast().last?.as(BinaryOperatorExprSyntax.self),
secondToLastExpression.operatorToken.tokenKind.isNilCoalescingOperator,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
else {
return super.visit(node)
}
let newNode = node.removingLast().removingLast().with(\.trailingTrivia, [])
correctionPositions.append(newNode.endPosition)
return super.visit(newNode)
}
}
}
private extension TokenKind {
var isNilCoalescingOperator: Bool {
self == .binaryOperator("??")
}
}

View File

@ -0,0 +1,119 @@
import Foundation
import SwiftSyntax
private let attributeNamesImplyingObjc: Set<String> = [
"IBAction", "IBOutlet", "IBInspectable", "GKInspectable", "IBDesignable", "NSManaged"
]
struct RedundantObjcAttributeRule: SwiftSyntaxRule, SubstitutionCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "redundant_objc_attribute",
name: "Redundant @objc Attribute",
description: "Objective-C attribute (@objc) is redundant in declaration",
kind: .idiomatic,
nonTriggeringExamples: RedundantObjcAttributeRuleExamples.nonTriggeringExamples,
triggeringExamples: RedundantObjcAttributeRuleExamples.triggeringExamples,
corrections: RedundantObjcAttributeRuleExamples.corrections
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: AttributeListSyntax) {
if let objcAttribute = node.violatingObjCAttribute {
violations.append(objcAttribute.positionAfterSkippingLeadingTrivia)
}
}
}
return Visitor(viewMode: .sourceAccurate)
}
func violationRanges(in file: SwiftLintFile) -> [NSRange] {
makeVisitor(file: file)
.walk(tree: file.syntaxTree, handler: \.violations)
.compactMap { violation in
let end = AbsolutePosition(utf8Offset: violation.position.utf8Offset + "@objc".count)
return file.stringView.NSRange(start: violation.position, end: end)
}
}
}
private extension AttributeListSyntax {
var objCAttribute: AttributeSyntax? {
lazy
.compactMap { $0.as(AttributeSyntax.self) }
.first { $0.attributeNameText == "objc" && $0.argument == nil }
}
var hasAttributeImplyingObjC: Bool {
contains { element in
guard let attributeName = element.as(AttributeSyntax.self)?.attributeNameText else {
return false
}
return attributeNamesImplyingObjc.contains(attributeName)
}
}
}
private extension Syntax {
var isFunctionOrStoredProperty: Bool {
if self.is(FunctionDeclSyntax.self) {
return true
} else if let variableDecl = self.as(VariableDeclSyntax.self),
variableDecl.bindings.allSatisfy({ $0.accessor == nil }) {
return true
} else {
return false
}
}
var functionOrVariableModifiers: ModifierListSyntax? {
if let functionDecl = self.as(FunctionDeclSyntax.self) {
return functionDecl.modifiers
} else if let variableDecl = self.as(VariableDeclSyntax.self) {
return variableDecl.modifiers
}
return nil
}
}
private extension AttributeListSyntax {
var violatingObjCAttribute: AttributeSyntax? {
guard let objcAttribute = objCAttribute else {
return nil
}
if hasAttributeImplyingObjC, parent?.is(ExtensionDeclSyntax.self) != true {
return objcAttribute
} else if parent?.is(EnumDeclSyntax.self) == true {
return nil
} else if parent?.isFunctionOrStoredProperty == true,
let parentClassDecl = parent?.parent?.parent?.parent?.parent?.as(ClassDeclSyntax.self),
parentClassDecl.attributes.contains(attributeNamed: "objcMembers") {
return parent?.functionOrVariableModifiers.isPrivateOrFileprivate == true ? nil : objcAttribute
} else if let parentExtensionDecl = parent?.parent?.parent?.parent?.parent?.as(ExtensionDeclSyntax.self),
parentExtensionDecl.attributes?.objCAttribute != nil {
return objcAttribute
} else {
return nil
}
}
}
extension RedundantObjcAttributeRule {
func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
var whitespaceAndNewlineOffset = 0
let nsCharSet = CharacterSet.whitespacesAndNewlines.bridge()
let nsContent = file.contents.bridge()
while nsCharSet
.characterIsMember(nsContent.character(at: violationRange.upperBound + whitespaceAndNewlineOffset)) {
whitespaceAndNewlineOffset += 1
}
let withTrailingWhitespaceAndNewlineRange = NSRange(location: violationRange.location,
length: violationRange.length + whitespaceAndNewlineOffset)
return (withTrailingWhitespaceAndNewlineRange, "")
}
}

View File

@ -1,4 +1,3 @@
// swiftlint:disable:next type_body_length
struct RedundantObjcAttributeRuleExamples {
static let nonTriggeringExamples = [
Example("@objc private var foo: String? {}"),
@ -14,7 +13,7 @@ struct RedundantObjcAttributeRuleExamples {
class Foo {
var bar: Any?
@objc
class Bar {
class Bar: NSObject {
@objc
var foo: Any?
}
@ -60,16 +59,76 @@ struct RedundantObjcAttributeRuleExamples {
Example("""
@objcMembers
class Foo {
@objc
class Bar: NSObject {
@objc var foo: Any
@objc var foo: Any?
}
}
"""),
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objc class Bar {}
}
"""),
Example("""
extension BlockEditorSettings {
@objc(addElementsObject:)
@NSManaged public func addToElements(_ value: BlockEditorSettingElement)
}
"""),
Example("""
@objcMembers
public class Foo: NSObject {
@objc
private func handler(_ notification: Notification) {
}
func registerForNotifications() {
NotificationCenter.default.addObserver(self, selector: #selector(handler(_:)), name: nil, object: nil)
}
}
"""),
Example("""
class Foo: NSObject { }
@objc extension Foo {
@objc enum Bar: Int {
case bar
}
var bar: Bar { .bar }
}
"""),
Example("""
class Foo: NSObject { }
@objc extension Foo {
@objc private enum Baz: Int {
case baz
}
private var baz: Baz { .baz }
}
"""),
Example("""
@objcMembers
internal class Foo: NSObject {
@objc
private var baz: Int = 1
var x: Any? {
value(forKey: "baz")
}
}
"""),
Example("""
@objcMembers
class Foo: NSObject {
@objc enum Bar: Int {
case bar
}
}
""")
]
@ -85,13 +144,13 @@ struct RedundantObjcAttributeRuleExamples {
Example("↓@objc @IBDesignable class Foo {}"),
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objc var bar: Any?
}
"""),
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objc var bar: Any?
@objc var foo: Any?
@objc
@ -121,7 +180,7 @@ struct RedundantObjcAttributeRuleExamples {
"""),
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objcMembers
class Bar: NSObject {
@objc var foo: Any
@ -155,23 +214,23 @@ struct RedundantObjcAttributeRuleExamples {
Example("↓@objc @IBDesignable class Foo {}"): Example("@IBDesignable class Foo {}"),
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objc var bar: Any?
}
"""):
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
var bar: Any?
}
"""),
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objc var bar: Any?
@objc var foo: Any?
@objc
class Bar {
class Bar: NSObject {
@objc
var foo2: Any?
}
@ -179,11 +238,11 @@ struct RedundantObjcAttributeRuleExamples {
"""):
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
var bar: Any?
var foo: Any?
@objc
class Bar {
class Bar: NSObject {
@objc
var foo2: Any?
}
@ -225,7 +284,7 @@ struct RedundantObjcAttributeRuleExamples {
"""),
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objcMembers
class Bar: NSObject {
@objc var foo: Any
@ -234,7 +293,7 @@ struct RedundantObjcAttributeRuleExamples {
"""):
Example("""
@objcMembers
class Foo {
class Foo: NSObject {
@objcMembers
class Bar: NSObject {
var foo: Any

View File

@ -0,0 +1,212 @@
import SwiftSyntax
struct RedundantOptionalInitializationRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "redundant_optional_initialization",
name: "Redundant Optional Initialization",
description: "Initializing an optional variable with nil is redundant",
kind: .idiomatic,
nonTriggeringExamples: [
Example("var myVar: Int?\n"),
Example("let myVar: Int? = nil\n"),
Example("var myVar: Int? = 0\n"),
Example("func foo(bar: Int? = 0) { }\n"),
Example("var myVar: Optional<Int>\n"),
Example("let myVar: Optional<Int> = nil\n"),
Example("var myVar: Optional<Int> = 0\n"),
// properties with body should be ignored
Example("""
var foo: Int? {
if bar != nil { }
return 0
}
"""),
// properties with a closure call
Example("""
var foo: Int? = {
if bar != nil { }
return 0
}()
"""),
// lazy variables need to be initialized
Example("lazy var test: Int? = nil"),
// local variables
Example("""
func funcName() {
var myVar: String?
}
"""),
Example("""
func funcName() {
let myVar: String? = nil
}
""")
],
triggeringExamples: triggeringExamples,
corrections: corrections
)
private static let triggeringExamples: [Example] = [
Example("var myVar: Int?↓ = nil\n"),
Example("var myVar: Optional<Int>↓ = nil\n"),
Example("var myVar: Int?↓=nil\n"),
Example("var myVar: Optional<Int>↓=nil\n)"),
Example("""
var myVar: String? = nil {
didSet { print("didSet") }
}
"""),
Example("""
func funcName() {
var myVar: String? = nil
}
""")
]
private static let corrections: [Example: Example] = [
Example("var myVar: Int?↓ = nil\n"): Example("var myVar: Int?\n"),
Example("var myVar: Optional<Int>↓ = nil\n"): Example("var myVar: Optional<Int>\n"),
Example("var myVar: Int?↓=nil\n"): Example("var myVar: Int?\n"),
Example("var myVar: Optional<Int>↓=nil\n"): Example("var myVar: Optional<Int>\n"),
Example("class C {\n#if true\nvar myVar: Int?↓ = nil\n#endif\n}"):
Example("class C {\n#if true\nvar myVar: Int?\n#endif\n}"),
Example("""
var myVar: Int? = nil {
didSet { }
}
"""):
Example("""
var myVar: Int? {
didSet { }
}
"""),
Example("""
var myVar: Int?=nil{
didSet { }
}
"""):
Example("""
var myVar: Int?{
didSet { }
}
"""),
Example("""
func foo() {
var myVar: String? = nil, b: Int
}
"""):
Example("""
func foo() {
var myVar: String?, b: Int
}
""")
]
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private extension RedundantOptionalInitializationRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: VariableDeclSyntax) {
guard node.bindingKeyword.tokenKind == .keyword(.var),
!node.modifiers.containsLazy else {
return
}
violations.append(contentsOf: node.bindings.compactMap(\.violationPosition))
}
}
final class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
private let locationConverter: SourceLocationConverter
private let disabledRegions: [SourceRange]
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: VariableDeclSyntax) -> DeclSyntax {
guard node.bindingKeyword.tokenKind == .keyword(.var),
!node.modifiers.containsLazy else {
return super.visit(node)
}
let violations = node.bindings
.compactMap { binding in
binding.violationPosition.map { ($0, binding) }
}
.filter { position, _ in
!position.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
}
guard violations.isNotEmpty else {
return super.visit(node)
}
correctionPositions.append(contentsOf: violations.map(\.0))
let violatingBindings = violations.map(\.1)
let newBindings = PatternBindingListSyntax(node.bindings.map { binding in
guard violatingBindings.contains(binding) else {
return binding
}
let newBinding = binding.with(\.initializer, nil)
if newBinding.accessor != nil {
return newBinding
}
if binding.trailingComma != nil {
return newBinding.with(\.typeAnnotation, binding.typeAnnotation?.with(\.trailingTrivia, Trivia()))
}
return newBinding.with(\.trailingTrivia, binding.initializer?.trailingTrivia ?? Trivia())
})
return super.visit(node.with(\.bindings, newBindings))
}
}
}
private extension PatternBindingSyntax {
var violationPosition: AbsolutePosition? {
guard let initializer,
let type = typeAnnotation,
initializer.isInitializingToNil,
type.isOptionalType else {
return nil
}
return type.endPositionBeforeTrailingTrivia
}
}
private extension InitializerClauseSyntax {
var isInitializingToNil: Bool {
value.is(NilLiteralExprSyntax.self)
}
}
private extension TypeAnnotationSyntax {
var isOptionalType: Bool {
if type.is(OptionalTypeSyntax.self) {
return true
}
if let type = type.as(SimpleTypeIdentifierSyntax.self), let genericClause = type.genericArgumentClause {
return genericClause.arguments.count == 1 && type.name.text == "Optional"
}
return false
}
}

View File

@ -0,0 +1,151 @@
import SwiftSyntax
struct RedundantSetAccessControlRule: ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "redundant_set_access_control",
name: "Redundant Access Control for Setter",
description: "Property setter access level shouldn't be explicit if " +
"it's the same as the variable access level",
kind: .idiomatic,
nonTriggeringExamples: [
Example("private(set) public var foo: Int"),
Example("public let foo: Int"),
Example("public var foo: Int"),
Example("var foo: Int"),
Example("""
private final class A {
private(set) var value: Int
}
"""),
Example("""
fileprivate class A {
public fileprivate(set) var value: Int
}
""", excludeFromDocumentation: true),
Example("""
extension Color {
public internal(set) static var someColor = Color.anotherColor
}
""")
],
triggeringExamples: [
Example("↓private(set) private var foo: Int"),
Example("↓fileprivate(set) fileprivate var foo: Int"),
Example("↓internal(set) internal var foo: Int"),
Example("↓public(set) public var foo: Int"),
Example("""
open class Foo {
open(set) open var bar: Int
}
"""),
Example("""
class A {
internal(set) var value: Int
}
"""),
Example("""
internal class A {
internal(set) var value: Int
}
"""),
Example("""
fileprivate class A {
fileprivate(set) var value: Int
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension RedundantSetAccessControlRule {
final class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
[FunctionDeclSyntax.self]
}
override func visitPost(_ node: VariableDeclSyntax) {
guard let modifiers = node.modifiers, let setAccessor = modifiers.setAccessor else {
return
}
let uniqueModifiers = Set(modifiers.map(\.name.tokenKind))
if uniqueModifiers.count != modifiers.count {
violations.append(modifiers.positionAfterSkippingLeadingTrivia)
return
}
if setAccessor.name.tokenKind == .keyword(.fileprivate),
modifiers.getAccessor == nil,
let closestDeclModifiers = node.closestDecl()?.modifiers {
let closestDeclIsFilePrivate = closestDeclModifiers.contains {
$0.name.tokenKind == .keyword(.fileprivate)
}
if closestDeclIsFilePrivate {
violations.append(modifiers.positionAfterSkippingLeadingTrivia)
return
}
}
if setAccessor.name.tokenKind == .keyword(.internal),
modifiers.getAccessor == nil,
let closesDecl = node.closestDecl(),
let closestDeclModifiers = closesDecl.modifiers {
let closestDeclIsInternal = closestDeclModifiers.isEmpty || closestDeclModifiers.contains {
$0.name.tokenKind == .keyword(.internal)
}
if closestDeclIsInternal {
violations.append(modifiers.positionAfterSkippingLeadingTrivia)
return
}
}
}
}
}
private extension SyntaxProtocol {
func closestDecl() -> DeclSyntax? {
if let decl = self.parent?.as(DeclSyntax.self) {
return decl
}
return parent?.closestDecl()
}
}
private extension DeclSyntax {
var modifiers: ModifierListSyntax? {
if let decl = self.as(ClassDeclSyntax.self) {
return decl.modifiers ?? ModifierListSyntax([])
} else if let decl = self.as(ActorDeclSyntax.self) {
return decl.modifiers ?? ModifierListSyntax([])
} else if let decl = self.as(StructDeclSyntax.self) {
return decl.modifiers ?? ModifierListSyntax([])
} else if let decl = self.as(ProtocolDeclSyntax.self) {
return decl.modifiers ?? ModifierListSyntax([])
} else if let decl = self.as(ExtensionDeclSyntax.self) {
return decl.modifiers ?? ModifierListSyntax([])
} else if let decl = self.as(EnumDeclSyntax.self) {
return decl.modifiers ?? ModifierListSyntax([])
}
return nil
}
}
private extension ModifierListSyntax {
var setAccessor: DeclModifierSyntax? {
first { $0.detail?.detail.tokenKind == .identifier("set") }
}
var getAccessor: DeclModifierSyntax? {
first { $0.detail == nil }
}
}

View File

@ -0,0 +1,112 @@
import SwiftSyntax
struct RedundantStringEnumValueRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "redundant_string_enum_value",
name: "Redundant String Enum Value",
description: "String enum values can be omitted when they are equal to the enumcase name",
kind: .idiomatic,
nonTriggeringExamples: [
Example("""
enum Numbers: String {
case one
case two
}
"""),
Example("""
enum Numbers: Int {
case one = 1
case two = 2
}
"""),
Example("""
enum Numbers: String {
case one = "ONE"
case two = "TWO"
}
"""),
Example("""
enum Numbers: String {
case one = "ONE"
case two = "two"
}
"""),
Example("""
enum Numbers: String {
case one, two
}
""")
],
triggeringExamples: [
Example("""
enum Numbers: String {
case one = "one"
case two = "two"
}
"""),
Example("""
enum Numbers: String {
case one = "one", two = "two"
}
"""),
Example("""
enum Numbers: String {
case one, two = "two"
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension RedundantStringEnumValueRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: EnumDeclSyntax) {
guard node.isStringEnum else {
return
}
let enumsWithExplicitValues = node.memberBlock.members
.flatMap { member -> EnumCaseElementListSyntax in
guard let enumCaseDecl = member.decl.as(EnumCaseDeclSyntax.self) else {
return EnumCaseElementListSyntax([])
}
return enumCaseDecl.elements
}
.filter { $0.rawValue != nil }
let redundantMembersPositions = enumsWithExplicitValues
.compactMap { element -> AbsolutePosition? in
guard let stringExpr = element.rawValue?.value.as(StringLiteralExprSyntax.self),
let segment = stringExpr.segments.onlyElement?.as(StringSegmentSyntax.self),
segment.content.text == element.identifier.text else {
return nil
}
return stringExpr.positionAfterSkippingLeadingTrivia
}
if redundantMembersPositions.count == enumsWithExplicitValues.count {
violations.append(contentsOf: redundantMembersPositions)
}
}
}
}
private extension EnumDeclSyntax {
var isStringEnum: Bool {
guard let inheritanceClause else {
return false
}
return inheritanceClause.inheritedTypeCollection.contains { elem in
elem.typeName.as(SimpleTypeIdentifierSyntax.self)?.typeName == "String"
}
}
}

View File

@ -1,10 +1,10 @@
import Foundation
import SourceKittenFramework
public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRule, ConfigurationProviderRule {
public var configuration = SeverityConfiguration(.warning)
struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "redundant_type_annotation",
name: "Redundant Type Annotation",
description: "Variables should not have redundant type annotation",
@ -76,7 +76,7 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul
]
)
public func validate(file: SwiftLintFile) -> [StyleViolation] {
func validate(file: SwiftLintFile) -> [StyleViolation] {
return violationRanges(in: file).map { range in
StyleViolation(
ruleDescription: Self.description,
@ -86,14 +86,14 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul
}
}
public func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
return (violationRange, "")
}
private let typeAnnotationPattern: String
private let expressionPattern: String
public init() {
init() {
typeAnnotationPattern =
":\\s*" + // semicolon and any number of whitespaces
"\\w+" // type name
@ -109,8 +109,8 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul
"[\\(\\.]?" // possible opening parenthesis or dot
}
public func violationRanges(in file: SwiftLintFile) -> [NSRange] {
let violatingRanges = file
func violationRanges(in file: SwiftLintFile) -> [NSRange] {
return file
.match(pattern: expressionPattern)
.filter {
$0.1 == [.keyword, .identifier, .typeidentifier, .identifier] ||
@ -122,8 +122,6 @@ public struct RedundantTypeAnnotationRule: OptInRule, SubstitutionCorrectableRul
file.match(pattern: typeAnnotationPattern,
excludingSyntaxKinds: SyntaxKind.commentAndStringKinds, range: $0.0).first
}
return violatingRanges
}
private func isFalsePositive(file: SwiftLintFile, range: NSRange) -> Bool {

View File

@ -1,15 +1,13 @@
import Foundation
import SourceKittenFramework
public struct RedundantVoidReturnRule: ConfigurationProviderRule, SubstitutionCorrectableASTRule {
public var configuration = SeverityConfiguration(.warning)
struct RedundantVoidReturnRule: ConfigurationProviderRule, SubstitutionCorrectableASTRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "redundant_void_return",
name: "Redundant Void Return",
description: "Returning Void in a function declaration is redundant.",
description: "Returning Void in a function declaration is redundant",
kind: .idiomatic,
nonTriggeringExamples: [
Example("func foo() {}\n"),
@ -60,8 +58,8 @@ public struct RedundantVoidReturnRule: ConfigurationProviderRule, SubstitutionCo
private let excludingKinds = SyntaxKind.allKinds.subtracting([.typeidentifier])
private let functionKinds = SwiftDeclarationKind.functionKinds.subtracting([.functionSubscript])
public func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
func validate(file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
return violationRanges(in: file, kind: kind, dictionary: dictionary).map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
@ -69,8 +67,8 @@ public struct RedundantVoidReturnRule: ConfigurationProviderRule, SubstitutionCo
}
}
public func violationRanges(in file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [NSRange] {
func violationRanges(in file: SwiftLintFile, kind: SwiftDeclarationKind,
dictionary: SourceKittenDictionary) -> [NSRange] {
guard functionKinds.contains(kind),
containsVoidReturnTypeBasedOnTypeName(dictionary: dictionary),
let nameOffset = dictionary.nameOffset,
@ -90,7 +88,7 @@ public struct RedundantVoidReturnRule: ConfigurationProviderRule, SubstitutionCo
return [match]
}
public func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
return (violationRange, "")
}

View File

@ -1,45 +1,29 @@
import SourceKittenFramework
import SwiftSyntax
public struct ReturnValueFromVoidFunctionRule: ConfigurationProviderRule, OptInRule, SourceKitFreeRule {
public var configuration = SeverityConfiguration(.warning)
struct ReturnValueFromVoidFunctionRule: ConfigurationProviderRule, OptInRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration<Self>(.warning)
public init() {}
public static let description = RuleDescription(
static let description = RuleDescription(
identifier: "return_value_from_void_function",
name: "Return Value from Void Function",
description: "Returning values from Void functions should be avoided.",
description: "Returning values from Void functions should be avoided",
kind: .idiomatic,
minSwiftVersion: .fiveDotOne,
nonTriggeringExamples: ReturnValueFromVoidFunctionRuleExamples.nonTriggeringExamples,
triggeringExamples: ReturnValueFromVoidFunctionRuleExamples.triggeringExamples
)
public func validate(file: SwiftLintFile) -> [StyleViolation] {
ReturnValueFromVoidFunctionVisitor()
.walk(file: file) { visitor in
visitor.violations(for: self, in: file)
}
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
ReturnValueFromVoidFunctionVisitor(viewMode: .sourceAccurate)
}
}
private final class ReturnValueFromVoidFunctionVisitor: SyntaxVisitor {
private var positions = [AbsolutePosition]()
private final class ReturnValueFromVoidFunctionVisitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: ReturnStmtSyntax) {
if node.expression != nil,
let functionNode = Syntax(node).enclosingFunction(),
functionNode.returnsVoid {
positions.append(node.positionAfterSkippingLeadingTrivia)
}
}
func violations(for rule: ReturnValueFromVoidFunctionRule, in file: SwiftLintFile) -> [StyleViolation] {
return positions.map { position in
StyleViolation(ruleDescription: type(of: rule).description,
severity: rule.configuration.severity,
location: Location(file: file, byteOffset: ByteCount(position.utf8Offset)))
functionNode.returnsVoid {
violations.append(node.positionAfterSkippingLeadingTrivia)
}
}
}

Some files were not shown because too many files have changed in this diff Show More