Compare commits

..

1 Commits

Author SHA1 Message Date
Marcelo Fabri e400174591 Rewrite `quick_discouraged_call` with SwiftSyntax 2022-11-06 19:28:10 -08:00
683 changed files with 9093 additions and 14123 deletions

View File

@ -1 +0,0 @@
.build

View File

@ -1,14 +1,8 @@
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 \

View File

@ -1 +1 @@
6.2.0
5.3.0

View File

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

View File

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

View File

@ -1,24 +0,0 @@
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

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

View File

@ -5,25 +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 "--- Build Danger"
- bazel build //tools:danger
- echo "+++ Run Danger"
- bundle exec danger --verbose
- ./bazel-bin/tools/danger --verbose
- label: "Analyze"
commands:
- echo "+++ Analyze"
- bazel test -c opt --test_output=streamed --test_timeout=1800 --spawn_strategy=local analyze
- label: "TSan Tests"
commands:
- echo "+++ Test"
- bazel test --test_output=errors --build_tests_only --features=tsan --test_timeout=1000 //Tests/...
- label: "Sourcery"
- bazel test --test_output=streamed --build_tests_only --features=tsan --test_timeout=1000 //Tests/...
- label: "TSan Runs"
commands:
- echo "+++ Run Sourcery"
- make --always-make sourcery
- echo "+++ Diff Files"
- git diff --quiet HEAD
- echo "--- Build"
- bazel build --config=release --features=tsan swiftlint
- echo "+++ Pre-cache SwiftLint Run"
- ./bazel-bin/swiftlint --progress --lenient
- echo "+++ Post-cache SwiftLint Run"
- ./bazel-bin/swiftlint --progress --lenient

View File

@ -9,10 +9,10 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3
- uses: actions/checkout@v2
- name: Extract DOCKER_TAG using tag name
if: startsWith(github.ref, 'refs/tags/')

View File

@ -0,0 +1,32 @@
name: update_swift_syntax
on:
workflow_dispatch:
schedule:
# Mondays at 1pm UTC (9am EDT)
- cron: "0 13 * * 1"
jobs:
update_swift_syntax:
runs-on: ubuntu-latest
steps:
- name: Checkout source
uses: actions/checkout@v2
- name: Update SwiftSyntax
run: ./tools/update-swift-syntax.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Check for changes
id: state
run: |
if ! git diff-index --quiet HEAD --; then
echo "dirty=true" >> $GITHUB_OUTPUT
fi
- name: Create PR
if: steps.state.outputs.dirty == 'true'
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27
with:
title: Update SwiftSyntax
delete-branch: true
branch: update-swift-syntax
branch-suffix: timestamp

2
.gitignore vendored
View File

@ -1,5 +1,6 @@
# Xcode
#
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
## Build generated
build/
@ -21,7 +22,6 @@ xcuserdata
*.moved-aside
*.xcuserstate
*.xcscmblueprint
default.profraw
## Obj-C/Swift specific
*.hmap

View File

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

View File

@ -1,14 +1,12 @@
@testable import SwiftLintBuiltInRules
@_spi(TestHelper)
@testable import SwiftLintCore
import SwiftLintFramework
import SwiftLintTestHelpers
import XCTest
// 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: SwiftLintTestCase {
class {{ rule.name }}GeneratedTests: XCTestCase {
func testWithDefaultConfiguration() {
verifyRule({{ rule.name }}.description)
}

View File

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

View File

@ -1,7 +0,0 @@
/// 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,5 +1,4 @@
included:
- Plugins
- Source
- Tests
excluded:
@ -8,55 +7,75 @@ analyzer_rules:
- unused_declaration
- unused_import
opt_in_rules:
- all
disabled_rules:
- anonymous_argument_in_multiline_closure
- anyobject_protocol
- 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
- 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
- prefer_self_in_static_references
- 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
- 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
attributes:
always_on_line_above:
- "@OptionGroup"
identifier_name:
excluded:
- id
@ -65,27 +84,16 @@ number_separator:
minimum_length: 5
file_name:
excluded:
- Exports.swift
- GeneratedTests.swift
- SwiftSyntax+SwiftLint.swift
- GeneratedTests.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/SwiftLintBuiltInRules/Rules/.+/\w+\.swift
included: Source/SwiftLintFramework/Rules/.+/\w+\.swift
name: Rule ID
message: Rule IDs must be all lowercase, snake case and not end with `rule`
regex: ^\s+identifier:\s*("\w+_rule"|"\S*[^a-z_]\S*")
regex: identifier:\s*("\w+_rule"|"\S*[^a-z_]\S*")
severity: error
fatal_error:
name: Fatal Error
@ -105,4 +113,3 @@ custom_rules:
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

89
BUILD
View File

@ -1,72 +1,32 @@
load("@build_bazel_rules_apple//apple:apple.bzl", "apple_universal_binary")
load(
"@build_bazel_rules_swift//swift:swift.bzl",
"swift_binary",
"swift_library",
)
load(
"@rules_xcodeproj//xcodeproj:defs.bzl",
"@com_github_buildbuddy_io_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 = [
":SwiftLintBuiltInRules",
":SwiftLintCore",
":SwiftLintExtraRules",
],
"@com_github_jpsim_sourcekitten//:SourceKittenFramework",
"@com_github_apple_swift_syntax//:optlibs",
"@sourcekitten_com_github_jpsim_yams//:Yams",
] + select({
"@platforms//os:linux": ["@com_github_krzyzanowskim_cryptoswift//:CryptoSwift"],
"//conditions:default": [],
}),
)
swift_library(
@ -90,33 +50,6 @@ swift_binary(
],
)
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 = "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(
@ -135,8 +68,6 @@ filegroup(
srcs = [
"BUILD",
"LICENSE",
"MODULE.bazel",
"//:DyldWarningWorkaroundSources",
"//:LintInputs",
"//Tests:BUILD",
"//bazel:release_files",

View File

@ -2,557 +2,6 @@
#### Breaking
* None.
#### Experimental
* None.
#### Enhancements
* Mention a rule's identifier in the console message that is printed when the
rule's associated configuration entry contains invalid values.
[SimplyDanny](https://github.com/SimplyDanny)
* Silence `xct_specific_matcher` rule on "one argument asserts" if there are
potential types or tuples involved in the comparison as types and tuples do
not conform to `Equatable`.
[SimplyDanny](https://github.com/SimplyDanny)
[#4990](https://github.com/realm/SwiftLint/issues/4990)
* Add `grouping` option to the `sorted_imports` rule allowing
to sort groups of imports defined by their preceding attributes
(e.g. `@testable`, `@_exported`, ...).
[hiltonc](https://github.com/hiltonc)
* Do not trigger `redundant_self_in_closure` rule when another idenfier `x` in
scope shadows the field accessed by `self.x` to avoid semantical changes.
[SimplyDanny](https://github.com/SimplyDanny)
[#5010](https://github.com/realm/SwiftLint/issues/5010)
#### Bug Fixes
* The option `validates_start_with_lowercase` can now be disabled by setting it
to `off`.
[SimplyDanny](https://github.com/SimplyDanny)
[#5036](https://github.com/realm/SwiftLint/issues/5036)
* Do not trigger `prefer_self_in_static_references` rule on `typealias`
declarations in classes.
[SimplyDanny](https://github.com/SimplyDanny)
[#5009](https://github.com/realm/SwiftLint/issues/5009)
* Do not trigger `prefer_self_in_static_references` rule on collection types in
classes, but on initializers like `[C]()` in all types.
[SimplyDanny](https://github.com/SimplyDanny)
[#5042](https://github.com/realm/SwiftLint/issues/5042)
* Fix false positives on `redundant_objc_attribute` rule for enums
and private members.
[Martin Redington](https://github.com/mildm8nnered)
[#4633](https://github.com/realm/SwiftLint/issues/4633)
* Fix autocorrect for `CGIntersectionRect` in `legacy_cggeometry_functions`
rule.
[Haoocen](https://github.com/Haoocen)
[#5023](https://github.com/realm/SwiftLint/pull/5023)
* Fix false positives on `sorted_first_last` rule when `first`/`last` have
a predicate.
[woxtu](https://github.com/woxtu)
[#3023](https://github.com/realm/SwiftLint/issues/3023)
## 0.52.2: Crisper Clearer Pleats
#### Breaking
* None.
#### Experimental
* None.
#### Enhancements
* Exclude simple assignments of the form `self.x = x` from being reported by
the `redundant_self_in_closure` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4988](https://github.com/realm/SwiftLint/issues/4988)
#### Bug Fixes
* Make `unhandled_throwing_task` opt-in instead of enabled by default. The rule
is still prone to false positives at this point, so this makes enabling the
rule a conscious decision by end-users.
[JP Simard](https://github.com/jpsim)
[#4987](https://github.com/realm/SwiftLint/issues/4987)
* Fix `unhandled_throwing_task` false positives when the `Task` is returned or
where the throwing code is handled in a `Result` initializer.
[JP Simard](https://github.com/jpsim)
[#4987](https://github.com/realm/SwiftLint/issues/4987)
## 0.52.1: Crisp Clear Pleats
#### Breaking
* None.
#### Experimental
* None.
#### Enhancements
* None.
#### Bug Fixes
* Let the `validates_start_with_lowercase` option in name configurations
expect a severity (warning or error). Not setting it disables the check.
Boolean values are now deprecated. A `true` value enables the check as an
error for the time being to keep the previous behavior.
[SimplyDanny](https://github.com/SimplyDanny)
[#2180](https://github.com/realm/SwiftLint/issues/2180)
* Fixed a false positive in `unhandled_throwing_task`.
[kylebshr](https://github.com/kylebshr)
[#4984](https://github.com/realm/SwiftLint/issues/4984)
* Fix Bazel release tarball for compiling on macOS.
[JP Simard](https://github.com/jpsim)
[#4985](https://github.com/realm/SwiftLint/issues/4985)
## 0.52.0: Crisp Clear Pleats
#### Breaking
* The `attributes` rule now expects attributes with arguments to be placed
on their own line above the declaration they are supposed to influence.
This applies to attributes with any kinds of arguments including single
key path arguments which were previously handled in a different way. This
behavior can be turned off by setting `attributes_with_arguments_always_on_line_above`
to `false.
[SimplyDanny](https://github.com/SimplyDanny)
[#4843](https://github.com/realm/SwiftLint/issues/4843)
* The internal module structure for SwiftLint has changed to split the
monolithic `SwiftLintFramework` into new `SwiftLintCore` for core linter
infrastructure, `SwiftLintBuiltInRules` for built-in rules and
`SwiftLintExtraRules` to add your own native rules to SwiftLint.
[JP Simard](https://github.com/jpsim)
#### Experimental
* None.
#### Enhancements
* Add new `superfluous_else` rule that triggers on `if`-statements when an
attached `else`-block can be removed, because all branches of the previous
`if`-block(s) would certainly exit the current scope already.
[SimplyDanny](https://github.com/SimplyDanny)
* Add `sorted_enum_cases` rule which warns when enum cases are not sorted.
[kimdv](https://github.com/kimdv)
* Add new `redundant_self_in_closure` rule that triggers in closures on
explicitly used `self` when it's actually not needed due to:
* Strongly captured `self` (`{ [self] in ... }`)
* Closure used in a struct declaration (`self` can always be omitted)
* Anonymous closures that are directly called (`{ ... }()`) as they are
definitly not escaping
* Weakly captured `self` with explicit unwrapping
[SimplyDanny](https://github.com/SimplyDanny)
[#59](https://github.com/realm/SwiftLint/issues/59)
* Extend `xct_specific_matcher` rule to check for boolean asserts on (un)equal
comparisons. The rule can be configured with the matchers that should trigger
rule violations. By default, all matchers trigger, but that can be limited to
just `one-argument-asserts` or `two-argument-asserts`.
[SimplyDanny](https://github.com/SimplyDanny)
[JP Simard](https://github.com/jpsim)
[#3726](https://github.com/realm/SwiftLint/issues/3726)
* Trigger `prefer_self_in_static_references` rule on more type references.
[SimplyDanny](https://github.com/SimplyDanny)
* Adds a new `reporters` command, to improve discoverability of reporters.
[Martin Redington](https://github.com/mildm8nnered)
[#4819](https://github.com/realm/SwiftLint/issues/4819)
* Adds `test_parent_classes` option to the `no_magic_numbers` rule.
Violations within test classes will now be ignored by default.
[Martin Redington](https://github.com/mildm8nnered)
[#4896](https://github.com/realm/SwiftLint/issues/4896)
* Stop enforcing calls to super from the override functions `setUp()`,
`tearDown()`, `setUpWithError()`, and `tearDownWithError()` in `XCTestCase`
subclasses.
[AndrewDMontgomery](https://github.com/andrewdmontgomery)
[#4875](https://github.com/realm/SwiftLint/pull/4875)
* Prepend `warning: ` to error messages so that they show in Xcode.
[whiteio](https://github.com/whiteio)
[#4923](https://github.com/realm/SwiftLint/issues/4923)
* The `attributes` rule received a new boolean option
`attributes_with_arguments_always_on_line_above` which is `true` by default.
Setting it to `false` ensures that attributes with arguments like
`@Persisted(primaryKey: true)` don't violate the rule if they are on the same
line with the variable declaration.
[SimplyDanny](https://github.com/SimplyDanny)
[#4843](https://github.com/realm/SwiftLint/issues/4843)
* Add new `unhandled_throwing_task` rule that triggers when a Task with an
implicit error type has unhandled trys or errors thrown inside its body.
This results in errors being silently discarded, which may be unexpected.
See this forum thread for more details: https://forums.swift.org/t/56066
[kylebshr](https://github.com/kylebshr)
#### Bug Fixes
* Fix `lower_acl_than_parent` rule rewriter by preserving leading whitespace.
[SimplyDanny](https://github.com/SimplyDanny)
[#4860](https://github.com/realm/SwiftLint/issues/4860)
* Ignore block comments in `let_var_whitespace` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4871](https://github.com/realm/SwiftLint/issues/4871)
* Fix false positives in `indentation_width` rule.
[Sven Münnich](https://github.com/svenmuennich)
* Do not trigger `reduce_boolean` on `reduce` methods with a first named
argument that is different from `into`.
[SimplyDanny](https://github.com/SimplyDanny)
[#4894](https://github.com/realm/SwiftLint/issues/4894)
* Work around dyld warning about duplicate SwiftSyntax classes.
[keith](https://github.com/keith)
[#4782](https://github.com/realm/SwiftLint/issues/4782)
* Improve lint times of SwiftLintPlugin by moving the
`excludedPaths(fileManager:)` operation out of the linting iterations.
[andyyhope](https://github.com/andyyhope)
[#4844](https://github.com/realm/SwiftLint/issues/4844)
## 0.51.0: bzllint
#### Breaking
* Deprecate the `unused_capture_list` rule in favor of the Swift compiler
warning. At the same time, make it an opt-in rule.
[Cyberbeni](https://github.com/Cyberbeni)
[#4656](https://github.com/realm/SwiftLint/issues/4656)
* Deprecate the `inert_defer` rule in favor of the Swift compiler warning.
At the same time, make it an opt-in rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4615](https://github.com/realm/SwiftLint/issues/4615)
* Interpret strings in `excluded` option of `identifier_name`,
`type_name` and `generic_type_name` rules as regular expression. Existing
configurations should remain working without notice as long as they don't
contain characters that must be escaped in regular expression.
[Moly](https://github.com/kyounh12)
[#4655](https://github.com/realm/SwiftLint/pull/4655)
#### Experimental
* None.
#### Enhancements
* Add `duplicate_conditions` rule which warns when a condition is duplicated
in separate branches of the same branching statement (if-else, or switch).
[1in1](https://github.com/1in1)
[#4666](https://github.com/realm/SwiftLint/issues/4666)
* Add local links to rule descriptions to every rule listed
in `Rule Directory.md`.
[kattouf](https://github.com/kattouf)
* Make forceExclude work with directly specified files.
[jimmya](https://github.com/jimmya)
[#4609](https://github.com/realm/SwiftLint/issues/4609)
* Adds `all` pseudo-rule for `opt_in_rules` - enables all opt in rules
that are not listed in `disabled_rules`
[Martin Redington](https://github.com/mildm8nnered)
[#4540](https://github.com/realm/SwiftLint/issues/4540)
* Separate analyzer rules as an independent section in the rule directory of
the reference.
[Ethan Wong](https://github.com/GetToSet)
[#4664](https://github.com/realm/SwiftLint/pull/4664)
* Add rule identifier to output of Emoji reporter.
[SimplyDanny](https://github.com/SimplyDanny)
[#4707](https://github.com/realm/SwiftLint/issues/4707)
* Add new `direct_return` rule that triggers on `return` statements returning
variables that have been declared in the statement before only.
[SimplyDanny](https://github.com/SimplyDanny)
* Add `period_spacing` opt-in rule that checks periods are not followed
by 2 or more spaces in comments.
[Julioacarrettoni](https://github.com/Julioacarrettoni)
[#4624](https://github.com/realm/SwiftLint/pull/4624)
* Allow to pass a rule identifier to the `swiftlint docs` command to open its
specific documentation website, e.g. `swiftlint docs for_where`.
[SimplyDanny](https://github.com/SimplyDanny)
[#4707](https://github.com/realm/SwiftLint/issues/4707)
* Allow new Quick APIs `aroundEach` and `justBeforeEach` for
`quick_discouraged_call`.
[David Steinacher](https://github.com/stonko1994)
[#4626](https://github.com/realm/SwiftLint/issues/4626)
* Add `relative-path` reporter to generate reports with relative file paths.
[Roya1v](https://github.com/roya1v)
[#4660](https://github.com/realm/SwiftLint/issues/4660)
* Let `number_separator` rule trigger on misplaced separators, e.g. `10_00`.
[SimplyDanny](https://github.com/SimplyDanny)
[#4637](https://github.com/realm/SwiftLint/issues/4637)
* Rewrite `multiline_arguments` rule using SwiftSyntax, ignoring trailing
closures.
[Marcelo Fabri](https://github.com/marcelofabri)
[#3399](https://github.com/realm/SwiftLint/issues/3399)
[#3605](https://github.com/realm/SwiftLint/issues/3605)
* Speed up linting by up to 6% updating to use a newer version of
`SwiftSyntax`.
[JP Simard](https://github.com/jpsim)
* Catch more valid `legacy_multiple` violations.
[JP Simard](https://github.com/jpsim)
* Catch more valid `no_magic_numbers` violations.
[JP Simard](https://github.com/jpsim)
* Add `blanket_disable_command` rule that checks whether
rules are re-enabled after being disabled.
[Martin Redington](https://github.com/mildm8nnered)
[#4731](https://github.com/realm/SwiftLint/pull/4731)
* Add `invalid_swiftlint_command` rule that validates
`// swiftlint:enable` and `disable` commands.
[Martin Redington](https://github.com/mildm8nnered)
[#4546](https://github.com/realm/SwiftLint/pull/4546)
* Improve `identifier_name` documentation.
[Martin Redington](https://github.com/mildm8nnered)
[#4767](https://github.com/realm/SwiftLint/issues/4767)
* Adds `include_multiline_strings` option to `indentation_width` rule.
[Martin Redington](https://github.com/mildm8nnered)
[#4248](https://github.com/realm/SwiftLint/issues/4248)
* Adds a new `summary` reporter, that displays the number of violations
of each rule in a text table.
[Martin Redington](https://github.com/mildm8nnered)
#### Bug Fixes
* Report violations in all `<scope>_length` rules when the error threshold is
smaller than the warning threshold.
[SimplyDanny](https://github.com/SimplyDanny)
[#4645](https://github.com/realm/SwiftLint/issues/4645)
* Consider custom attributes in `attributes` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4599](https://github.com/realm/SwiftLint/issues/4599)
* Fix whitespaces issue in auto-fix of `redundant_optional_initialization`
rule when multiple variable declaration are involved.
[SimplyDanny](https://github.com/SimplyDanny)
[#4794](https://github.com/realm/SwiftLint/issues/4794)
* Stop triggering `strict_fileprivate` rule on symbols implementing a protocol
in the same file.
[SimplyDanny](https://github.com/SimplyDanny)
[#4692](https://github.com/realm/SwiftLint/issues/4692)
* Fix false positives on `private_subject` rule when using
subjects inside functions.
[Marcelo Fabri](https://github.com/marcelofabri)
[#4643](https://github.com/realm/SwiftLint/issues/4643)
* Fix for compiler directives masking subsequent `opening_brace`
violations.
[Martin Redington](https://github.com/mildm8nnered)
[#3712](https://github.com/realm/SwiftLint/issues/3712)
* Rewrite `explicit_type_interface` rule with SwiftSyntax fixing a
false-positive in if-case-let statements.
[SimplyDanny](https://github.com/SimplyDanny)
[#4548](https://github.com/realm/SwiftLint/issues/4548)
* Stop triggering `unused_capture_list` on captured variable that is only
referenced by a shorthand optional binding (`if let capturedVar { ... }`).
[SimplyDanny](https://github.com/SimplyDanny)
[#4804](https://github.com/realm/SwiftLint/issues/4804)
* Ensure that negative literals in initializers do not trigger
`no_magic_numbers` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4677](https://github.com/realm/SwiftLint/issues/4677)
* Fix caching of `indentation_width` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4121](https://github.com/realm/SwiftLint/issues/4121)
* Updated JUnit reporter to output error count and warning count.
[patricks](https://github.com/patricks)
[#4725](https://github.com/realm/SwiftLint/pull/4725)
* Fix correction on `lower_acl_than_parent` rule for `open` declarations.
[Marcelo Fabri](https://github.com/marcelofabri)
[#4753](https://github.com/realm/SwiftLint/issues/4753)
* Fix `void_return` rule to support async and async throws functions.
[Mathias Schreck](https://github.com/lo1tuma)
[#4772](https://github.com/realm/SwiftLint/issues/4772)
* Fix false positives in `attributes` rule when using property wrappers
with keypath arguments.
[JP Simard](https://github.com/jpsim)
* Fix for `superfluous_disable_command` not being completely disabled
by `disable` commands.
[Martin Redington](https://github.com/mildm8nnered)
[#4788](https://github.com/realm/SwiftLint/issues/4788)
* Fixed correction for `trailing_comma` rule wrongly removing trailing
comments.
[Martin Redington](https://github.com/mildm8nnered)
[#4814](https://github.com/realm/SwiftLint/issues/4814)
## 0.50.3: Bundle of Towels
#### Breaking
* None.
#### Experimental
* None.
#### Enhancements
* The `SwiftLintPlugin` SwiftPM plugin now uses a prebuilt binary on
macOS.
[Tony Arnold](https://github.com/tonyarnold)
[JP Simard](https://github.com/jpsim)
[#4558](https://github.com/realm/SwiftLint/issues/4558)
* Don't trigger `shorthand_operator` violations inside a shorthand
operator function declaration.
[Marcelo Fabri](https://github.com/marcelofabri)
[#4611](https://github.com/realm/SwiftLint/issues/4611)
* The `balanced_xctest_lifecycle`, `single_test_class`,
`empty_xctest_method` and `test_case_accessibility` rules will now be
applied to subclasses of `QuickSpec`, as well as `XCTestCase`, by
default.
[Martin Redington](https://github.com/mildm8nnered)
* Add `test_parent_classes` option to `balanced_xctest_lifecycle`,
`single_test_class` and `empty_xctest_method` rules.
[Martin Redington](https://github.com/mildm8nnered)
[#4200](https://github.com/realm/SwiftLint/issues/4200)
* Show warnings in the console for Analyzer rules that are listed in the
`opt_in_rules` configuration section.
[SimplyDanny](https://github.com/SimplyDanny)
[#4612](https://github.com/realm/SwiftLint/issues/4612)
#### Bug Fixes
* Fix configuration parsing error in `unused_declaration` rule.
[SimplyDanny](https://github.com/SimplyDanny)
[#4612](https://github.com/realm/SwiftLint/issues/4612)
* Skip `defer` statements being last in an `#if` block if the `#if`
statement is not itself the last statement in a block.
[SimplyDanny](https://github.com/SimplyDanny)
[#4615](https://github.com/realm/SwiftLint/issues/4615)
* Fix false positives in `empty_enum_arguments` when the called
expression is an identifier or an init call.
[Steffen Matthischke](https://github.com/heeaad)
[#4597](https://github.com/realm/SwiftLint/issues/4597)
* Fix correction issue in `comma` when there was too much whitespace
following the comma.
[JP Simard](https://github.com/jpsim)
## 0.50.1: Artisanal Clothes Pegs Fixup Edition
#### Breaking
* None.
#### Experimental
* None.
#### Enhancements
* Moved the validation of doc comments in local scopes out of
`orphaned_doc_comment` and into a new opt-in `local_doc_comment` rule.
[JP Simard](https://github.com/jpsim)
[#4573](https://github.com/realm/SwiftLint/issues/4573)
* SwiftLint's Swift Package Build Tool Plugin will now only scan files
in the target being built.
[Tony Arnold](https://github.com/tonyarnold)
[#4406](https://github.com/realm/SwiftLint/pull/4406)
#### Bug Fixes
* Fix building with `swift build -c release`.
[JP Simard](https://github.com/jpsim)
[#4559](https://github.com/realm/SwiftLint/issues/4559)
[#4560](https://github.com/realm/SwiftLint/issues/4560)
* Fix false positives in `lower_acl_than_parent` when the nominal parent
is an extension.
[Steffen Matthischke](https://github.com/heeaad)
[#4564](https://github.com/realm/SwiftLint/issues/4564)
* Fix `minimum_fraction_length` handling in `number_separator`.
[JP Simard](https://github.com/jpsim)
[#4576](https://github.com/realm/SwiftLint/issues/4576)
* Fix false positives in `closure_spacing`.
[JP Simard](https://github.com/jpsim)
[#4565](https://github.com/realm/SwiftLint/issues/4565)
[#4582](https://github.com/realm/SwiftLint/issues/4582)
* Fix line count calculation for multiline string literals.
[JP Simard](https://github.com/jpsim)
[#4585](https://github.com/realm/SwiftLint/issues/4585)
* Fix false positives in `unused_closure_parameter` when using
identifiers with backticks.
[JP Simard](https://github.com/jpsim)
[#4588](https://github.com/realm/SwiftLint/issues/4588)
* Fix `type_name` regression where names with backticks would trigger
violations.
[JP Simard](https://github.com/jpsim)
[#4571](https://github.com/realm/SwiftLint/issues/4571)
## 0.50.0: Artisanal Clothes Pegs
#### Breaking
* SwiftLint now requires Swift 5.7 or higher to build.
[JP Simard](https://github.com/jpsim)
@ -565,17 +14,16 @@
in a future release because it is now handled by the Swift compiler.
[JP Simard](https://github.com/jpsim)
* Built-in SwiftLint rules are no longer marked as `public` in
SwiftLintFramework. This only impacts the programmatic API for the
SwiftLintFramework module.
[JP Simard](https://github.com/jpsim)
#### Experimental
* None.
#### Enhancements
* Add `no_magic_numbers` rule to avoid "Magic Numbers".
[Henrik Storch](https://github.com/thisisthefoxe)
[#4031](https://github.com/realm/SwiftLint/issues/4024)
* SwiftSyntax libraries have been updated from the previous 5.6 release and now
use the new parser written in Swift.
Swift 5.7+ features should now be parsed more accurately.
@ -588,12 +36,25 @@
[JP Simard](https://github.com/jpsim)
[#4031](https://github.com/realm/SwiftLint/issues/4031)
* Add ability to filter rules for `generate-docs` subcommand.
[kattouf](https://github.com/kattouf)
* Add new `excludes_trivial_init` configuration for `missing_docs` rule
to exclude initializers without any parameters.
[Marcelo Fabri](https://github.com/marcelofabri)
[#4107](https://github.com/realm/SwiftLint/issues/4107)
* Add new `ns_number_init_as_function_reference` rule to catch `NSNumber.init`
and `NSDecimalNumber.init` being used as function references since it
can cause the wrong initializer to be used, causing crashes. See
https://github.com/apple/swift/issues/51036 for more info.
[Marcelo Fabri](https://github.com/marcelofabri)
* Rewrite some rules with SwiftSyntax, fixing some false positives and catching
more violations:
- `anonymous_argument_in_multiline_closure`
- `array_init`
- `attributes`
- `balanced_xctest_lifecycle`
- `block_based_kvo`
- `class_delegate_protocol`
- `closing_brace`
@ -658,37 +119,32 @@
- `legacy_objc_type`
- `legacy_random`
- `lower_acl_than_parent`
- `multiline_arguments_brackets`
- `multiline_parameters`
- `multiple_closures_with_trailing_closure`
- `multiline_parameters`
- `no_extension_access_modifier`
- `no_fallthrough_only`
- `no_space_in_method_call`
- `notification_center_detachment`
- `nslocalizedstring_key`
- `nslocalizedstring_require_bundle`
- `nsobject_prefer_isequal`
- `number_separator`
- `object_literal`
- `operator_whitespace`
- `optional_enum_case_matching`
- `orphaned_doc_comment`
- `overridden_super_call`
- `override_in_extension`
- `pattern_matching_keywords`
- `prefer_nimble`
- `prefer_self_in_static_references`
- `prefer_self_type_over_type_of_self`
- `prefer_zero_over_explicit_init`
- `prefixed_toplevel_constant`
- `private_action`
- `private_outlet`
- `private_over_fileprivate`
- `private_outlet`
- `private_subject`
- `private_unit_test`
- `prohibited_interface_builder`
- `prohibited_super_call`
- `protocol_property_accessors_order`
- `quick_discouraged_call`
- `quick_discouraged_focused_test`
- `quick_discouraged_pending_test`
- `raw_value_for_camel_cased_codable_enum`
@ -697,11 +153,10 @@
- `redundant_discardable_let`
- `redundant_nil_coalescing`
- `redundant_objc_attribute`
- `redundant_optional_initialization`
- `redundant_set_access_control`
- `redundant_optional_initialization`
- `redundant_string_enum_value`
- `required_deinit`
- `required_enum_case`
- `return_arrow_whitespace`
- `self_in_property_initialization`
- `shorthand_operator`
@ -744,20 +199,6 @@
commands to account for the changes.
[JP Simard](https://github.com/jpsim)
* Add ability to filter rules for `generate-docs` subcommand.
[kattouf](https://github.com/kattouf)
* Add new `excludes_trivial_init` configuration for `missing_docs` rule
to exclude initializers without any parameters.
[Marcelo Fabri](https://github.com/marcelofabri)
[#4107](https://github.com/realm/SwiftLint/issues/4107)
* Add new `ns_number_init_as_function_reference` rule to catch `NSNumber.init`
and `NSDecimalNumber.init` being used as function references since it
can cause the wrong initializer to be used, causing crashes. See
https://github.com/apple/swift/issues/51036 for more info.
[Marcelo Fabri](https://github.com/marcelofabri)
* Add `accessibility_trait_for_button` rule to warn if a SwiftUI
View has a tap gesture added to it without having the button or
link accessibility trait.
@ -770,10 +211,6 @@
messages.
[JP Simard](https://github.com/jpsim)
* The `self_binding` rule now catches shorthand optional bindings (for example
`if let self {}`) when using a `bind_identifier` different than `self`.
[Marcelo Fabri](https://github.com/marcelofabri)
* Add `library_content_provider` file type to `file_types_order` rule
to allow `LibraryContentProvider` to be ordered independent from `main_type`.
[dahlborn](https://github.com/dahlborn)
@ -810,13 +247,6 @@
* Print violations in realtime if `--progress` and `--output` are both set.
[JP Simard](https://github.com/jpsim)
* Trigger `prefer_self_in_static_references` rule on more type references like:
* Key paths (e.g. `\MyType.myVar` -> `\Self.myVar`)
* Computed properties (e.g. `var i: Int { MyType.myVar )` -> `var i: Int { Self.myVar }`)
* Constructor calls (e.g. `MyType()` -> `Self()`)
[SimplyDanny](https://github.com/SimplyDanny)
* Update `for_where` rule, adding a new configuration
`allow_for_as_filter` to allow using `for in` with a single `if` inside
when there's a `return` statement inside the `if`'s body.
@ -841,28 +271,6 @@
* Report how much memory was used when `--benchmark` is specified.
[JP Simard](https://github.com/jpsim)
* Adds `NSError` to the list of types in `discouraged_direct_init`.
[jszumski](https://github.com/jszumski)
[#4508](https://github.com/realm/SwiftLint/issues/4508)
* Fix SwiftLint support on Xcode Cloud.
[JagCesar](https://github.com/JagCesar)
[westerlund](https://github.com/westerlund)
[#4484](https://github.com/realm/SwiftLint/issues/4484)
* Add `no_magic_numbers` rule to avoid "Magic Numbers".
[Henrik Storch](https://github.com/thisisthefoxe)
[#4031](https://github.com/realm/SwiftLint/issues/4024)
* Add new option `only_enforce_before_trivial_lines` to
`vertical_whitespace_closing_braces` rule. It restricts
the rule to apply only before trivial lines (containing
only closing braces, brackets and parentheses). This
allows empty lines before non-trivial lines of code
(e.g. if-else-statements).
[benjamin-kramer](https://github.com/benjamin-kramer)
[#3940](https://github.com/realm/SwiftLint/issues/3940)
#### Bug Fixes
* Respect `validates_start_with_lowercase` option when linting function names.
@ -905,11 +313,6 @@
[SimplyDanny](https://github.com/SimplyDanny)
[#4234](https://github.com/realm/SwiftLint/issues/4234)
* Fix false-positives from `multiline_arguments_brackets` when a function call has a
single line trailing closure.
[CraigSiemens](https://github.com/CraigSiemens)
[#4510](https://github.com/realm/SwiftLint/issues/4510)
## 0.49.1: Buanderie Principale
_Note: The default branch for the SwiftLint git repository was renamed from
@ -1194,6 +597,15 @@ macOS < 12.
#### Enhancements
* Add new option `only_enforce_before_trivial_lines` to
`vertical_whitespace_closing_braces` rule. It restricts
the rule to apply only before trivial lines (containing
only closing braces, brackets and parentheses). This
allows empty lines before non-trivial lines of code
(e.g. if-else-statements).
[benjamin-kramer](https://github.com/benjamin-kramer)
[#3940](https://github.com/realm/SwiftLint/issues/3940)
* Add type-checked analyzer rule version of `ArrayInitRule` named
`TypesafeArrayInitRule` with identifier `typesafe_array_init` that
avoids the false positives present in the lint rule.

View File

@ -1,9 +1,3 @@
## 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
@ -64,7 +58,7 @@ $ make docker_test
## Rules
New rules should be added in the `Source/SwiftLintBuiltInRules/Rules` directory.
New rules should be added in the `Source/SwiftLintFramework/Rules` directory.
Rules should conform to either the `Rule` or `ASTRule` protocols.
@ -104,11 +98,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/main/Source/SwiftLintBuiltInRules/Rules/Idiomatic/ForceCastRule.swift)
See [`ForceCastRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintFramework/Rules/Idiomatic/ForceCastRule.swift)
for a rule that allows severity configuration,
[`FileLengthRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintBuiltInRules/Rules/Metrics/FileLengthRule.swift)
[`FileLengthRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintFramework/Rules/Metrics/FileLengthRule.swift)
for a rule that has multiple severity levels,
[`IdentifierNameRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintBuiltInRules/Rules/Style/IdentifierNameRule.swift)
[`IdentifierNameRule`](https://github.com/realm/SwiftLint/blob/main/Source/SwiftLintFramework/Rules/Style/IdentifierNameRule.swift)
for a rule that allows name evaluation configuration:
``` yaml
@ -180,5 +174,10 @@ To bring up a new Buildkite worker from MacStadium:
`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. Add `echo "build --remote_cache=grpc://<creds>@swiftlint-ci.jpsim.com:9092" > ci.bazelrc`
to `/opt/homebrew/etc/buildkite-agent/hooks/pre-command`, replacing `<creds>` with the
bazel-remote credentials
1. Configure and launch buildkite agent: `brew info buildkite-agent` /
https://buildkite.com/organizations/swiftlint/agents#setup-macos
1. On the `swiftlint-ci.jpsim.com` machine only:
`docker run -d -u 1000:1000 -v /tmp/swiftlint-bazel-remote-cache:/data -v ~/Desktop/bazel-remote.htpasswd:/etc/bazel-remote/htpasswd -p 9090:8080 -p 9092:9092 buchgr/bazel-remote-cache --htpasswd_file=/etc/bazel-remote/htpasswd --max_size=5`

View File

@ -16,7 +16,7 @@ has_app_changes = !modified_files.grep(/Source/).empty?
has_test_changes = !modified_files.grep(/Tests/).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(/\.bazelrc|\.bazelversion|WORKSPACE|bazel\/|BUILD|MODULE\.bazel/).empty?
has_bazel_changes = !modified_files.grep(/\.bazelrc|WORKSPACE|bazel\/|BUILD/).empty?
# Add a CHANGELOG entry for app changes
if !modified_files.include?('CHANGELOG.md') && has_app_changes

View File

@ -14,6 +14,8 @@ 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 --product swiftlint
@ -30,10 +32,9 @@ 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/libswiftCore.so /usr/lib
COPY --from=builder /usr/lib/swift/linux/lib_InternalSwiftSyntaxParser.so /usr/lib
COPY --from=builder /executables/* /usr/bin
RUN swiftlint version
RUN echo "_ = 0" | swiftlint --use-stdin
CMD ["swiftlint"]

View File

@ -1,14 +1,15 @@
GEM
remote: https://rubygems.org/
specs:
CFPropertyList (3.0.6)
CFPropertyList (3.0.5)
rexml
activesupport (7.0.4.3)
activesupport (6.1.6.1)
concurrent-ruby (~> 1.0, >= 1.0.2)
i18n (>= 1.6, < 2)
minitest (>= 5.1)
tzinfo (~> 2.0)
addressable (2.8.4)
zeitwerk (~> 2.3)
addressable (2.8.1)
public_suffix (>= 2.0.2, < 6.0)
algoliasearch (1.27.5)
httpclient (~> 2.8, >= 2.8.3)
@ -19,15 +20,15 @@ GEM
cork
nap
open4 (~> 1.3)
cocoapods (1.12.1)
cocoapods (1.11.3)
addressable (~> 2.8)
claide (>= 1.0.2, < 2.0)
cocoapods-core (= 1.12.1)
cocoapods-core (= 1.11.3)
cocoapods-deintegrate (>= 1.0.3, < 2.0)
cocoapods-downloader (>= 1.6.0, < 2.0)
cocoapods-downloader (>= 1.4.0, < 2.0)
cocoapods-plugins (>= 1.0.0, < 2.0)
cocoapods-search (>= 1.0.0, < 2.0)
cocoapods-trunk (>= 1.6.0, < 2.0)
cocoapods-trunk (>= 1.4.0, < 2.0)
cocoapods-try (>= 1.1.0, < 2.0)
colored2 (~> 3.1)
escape (~> 0.0.4)
@ -35,10 +36,10 @@ GEM
gh_inspector (~> 1.0)
molinillo (~> 0.8.0)
nap (~> 1.0)
ruby-macho (>= 2.3.0, < 3.0)
ruby-macho (>= 1.0, < 3.0)
xcodeproj (>= 1.21.0, < 2.0)
cocoapods-core (1.12.1)
activesupport (>= 5.0, < 8)
cocoapods-core (1.11.3)
activesupport (>= 5.0, < 7)
addressable (~> 2.8)
algoliasearch (~> 1.0)
concurrent-ruby (~> 1.1)
@ -57,42 +58,61 @@ GEM
netrc (~> 0.11)
cocoapods-try (1.2.0)
colored2 (3.1.2)
concurrent-ruby (1.2.2)
concurrent-ruby (1.1.10)
cork (0.3.0)
colored2 (~> 3.1)
danger (9.2.0)
danger (8.6.1)
claide (~> 1.0)
claide-plugins (>= 0.9.2)
colored2 (~> 3.1)
cork (~> 0.1)
faraday (>= 0.9.0, < 3.0)
faraday (>= 0.9.0, < 2.0)
faraday-http-cache (~> 2.0)
git (~> 1.7)
kramdown (~> 2.3)
kramdown-parser-gfm (~> 1.0)
no_proxy_fix
octokit (~> 5.0)
octokit (~> 4.7)
terminal-table (>= 1, < 4)
escape (0.0.4)
ethon (0.16.0)
ethon (0.15.0)
ffi (>= 1.15.0)
faraday (2.7.4)
faraday-net_http (>= 2.0, < 3.1)
faraday (1.10.2)
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)
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.4.1)
faraday (>= 0.8)
faraday-net_http (3.0.2)
faraday-httpclient (1.0.1)
faraday-multipart (1.0.4)
multipart-post (~> 2)
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)
ffi (1.15.5)
fourflusher (2.3.1)
fuzzy_match (2.0.4)
gh_inspector (1.1.3)
git (1.18.0)
git (1.12.0)
addressable (~> 2.8)
rchardet (~> 1.8)
httpclient (2.8.3)
i18n (1.12.0)
concurrent-ruby (~> 1.0)
jazzy (0.14.3)
jazzy (0.14.2)
cocoapods (~> 1.5)
mustache (~> 1.1)
open4 (~> 1.3)
@ -102,26 +122,27 @@ GEM
sassc (~> 2.1)
sqlite3 (~> 1.3)
xcinvoke (~> 0.3.0)
json (2.6.3)
json (2.6.2)
kramdown (2.4.0)
rexml
kramdown-parser-gfm (1.1.0)
kramdown (~> 2.0)
liferaft (0.0.6)
minitest (5.18.0)
minitest (5.16.3)
molinillo (0.8.0)
multipart-post (2.2.3)
mustache (1.1.1)
nanaimo (0.3.0)
nap (1.1.0)
netrc (0.11.0)
no_proxy_fix (0.1.2)
octokit (5.6.1)
octokit (4.25.1)
faraday (>= 1, < 3)
sawyer (~> 0.9)
open4 (1.3.4)
public_suffix (4.0.7)
rchardet (1.8.0)
redcarpet (3.6.0)
redcarpet (3.5.1)
rexml (3.2.5)
rouge (3.30.0)
ruby-macho (2.5.1)
@ -131,14 +152,14 @@ GEM
sawyer (0.9.2)
addressable (>= 2.3.5)
faraday (>= 0.17.3, < 3)
sqlite3 (1.6.2-arm64-darwin)
sqlite3 (1.4.4)
terminal-table (3.0.2)
unicode-display_width (>= 1.1.1, < 3)
typhoeus (1.4.0)
ethon (>= 0.9.0)
tzinfo (2.0.6)
tzinfo (2.0.5)
concurrent-ruby (~> 1.0)
unicode-display_width (2.4.2)
unicode-display_width (2.2.0)
xcinvoke (0.3.0)
liferaft (~> 0.0.6)
xcodeproj (1.22.0)
@ -148,10 +169,11 @@ GEM
colored2 (~> 3.1)
nanaimo (~> 0.3.0)
rexml (~> 3.2.4)
zeitwerk (2.6.0)
PLATFORMS
arm64-darwin-21
arm64-darwin-22
ruby
DEPENDENCIES
cocoapods
@ -159,4 +181,4 @@ DEPENDENCIES
jazzy
BUNDLED WITH
2.4.12
2.3.8

View File

@ -1,27 +0,0 @@
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,7 +7,10 @@ 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
@ -30,18 +33,14 @@ VERSION_STRING=$(shell ./tools/get-version)
all: build
sourcery: Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift Source/SwiftLintCore/Models/ReportersList.swift Tests/GeneratedTests/GeneratedTests.swift
sourcery: Source/SwiftLintFramework/Models/PrimaryRuleList.swift Tests/GeneratedTests/GeneratedTests.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
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/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
Tests/GeneratedTests/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/GeneratedTests/GeneratedTests.swift
test: clean_xcode
@ -63,18 +62,25 @@ analyze_autocorrect: write_xcodebuild_log
clean:
rm -f "$(OUTPUT_PACKAGE)"
rm -rf "$(TEMPORARY_FOLDER)"
rm -f "./*.zip" "bazel.tar.gz" "bazel.tar.gz.sha256"
rm -f "./*.zip"
swift package clean
clean_xcode:
$(BUILD_TOOL) $(XCODEFLAGS) -configuration Test clean
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)"
build_x86_64:
swift build $(SWIFT_BUILD_FLAGS) --arch x86_64 --product swiftlint
build_arm64:
swift build $(SWIFT_BUILD_FLAGS) --arch arm64 --product swiftlint
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)"
strip -rSTX "$(SWIFTLINT_EXECUTABLE)"
build_with_disable_sandbox:
@ -117,11 +123,10 @@ zip_linux: docker_image
zip_linux_release:
$(eval TMP_FOLDER := $(shell mktemp -d))
docker run --platform linux/amd64 "ghcr.io/realm/swiftlint:$(VERSION_STRING)" cat /usr/bin/swiftlint > "$(TMP_FOLDER)/swiftlint"
docker run "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))
@ -137,11 +142,13 @@ bazel_release:
bazel build :release
mv bazel-bin/bazel.tar.gz bazel-bin/bazel.tar.gz.sha256 .
release: bazel_release package portable_zip spm_artifactbundle_macos zip_linux_release
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.7-focal swift test --parallel
docker run -v `pwd`:`pwd` -w `pwd` --name swiftlint --rm swift:5.6-focal swift test --parallel
docker_htop:
docker run --platform linux/amd64 -it --rm --pid=container:swiftlint terencewestphal/htop || reset
@ -152,8 +159,8 @@ display_compilation_time:
publish:
brew update && brew bump-formula-pr --tag=$(shell git describe --tags) --revision=$(shell git rev-parse HEAD) swiftlint
bundle install
bundle exec pod trunk push SwiftLint.podspec
# Workaround for https://github.com/CocoaPods/CocoaPods/issues/11185
arch -arch x86_64 pod trunk push SwiftLint.podspec
docs:
swift run swiftlint generate-docs
@ -163,30 +170,18 @@ docs:
get_version:
@echo "$(VERSION_STRING)"
release:
push_version:
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/## 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)"
@sed 's/__VERSION__/$(NEW_VERSION)/g' tools/Version.swift.template > Source/SwiftLintFramework/Models/Version.swift
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,77 +1,70 @@
{
"pins" : [
"object": {
"pins": [
{
"identity" : "collectionconcurrencykit",
"kind" : "remoteSourceControl",
"location" : "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
"state" : {
"revision" : "b4f23e24b5a1bff301efc5e70871083ca029ff95",
"version" : "0.2.0"
"package": "CollectionConcurrencyKit",
"repositoryURL": "https://github.com/JohnSundell/CollectionConcurrencyKit.git",
"state": {
"branch": null,
"revision": "b4f23e24b5a1bff301efc5e70871083ca029ff95",
"version": "0.2.0"
}
},
{
"identity" : "cryptoswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : {
"revision" : "32f641cf24fc7abc1c591a2025e9f2f572648b0f",
"version" : "1.7.2"
"package": "SourceKitten",
"repositoryURL": "https://github.com/jpsim/SourceKitten.git",
"state": {
"branch": null,
"revision": "a9e6df65d8e31e0fa6e8a05ffe40ecd54a645871",
"version": null
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/SourceKitten.git",
"state" : {
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
"version" : "0.34.1"
"package": "swift-argument-parser",
"repositoryURL": "https://github.com/apple/swift-argument-parser.git",
"state": {
"branch": null,
"revision": "9f39744e025c7d377987f30b03770805dcb0bcd1",
"version": "1.1.4"
}
},
{
"identity" : "swift-argument-parser",
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-argument-parser.git",
"state" : {
"revision" : "4ad606ba5d7673ea60679a61ff867cc1ff8c8e86",
"version" : "1.2.1"
"package": "SwiftSyntax",
"repositoryURL": "https://github.com/apple/swift-syntax.git",
"state": {
"branch": null,
"revision": "1c4f45b54825ed5192c31d495686ac5b57efff70",
"version": null
}
},
{
"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"
"package": "SwiftyTextTable",
"repositoryURL": "https://github.com/scottrhoyt/SwiftyTextTable.git",
"state": {
"branch": null,
"revision": "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
"version": "0.9.0"
}
},
{
"identity" : "swiftytexttable",
"kind" : "remoteSourceControl",
"location" : "https://github.com/scottrhoyt/SwiftyTextTable.git",
"state" : {
"revision" : "c6df6cf533d120716bff38f8ff9885e1ce2a4ac3",
"version" : "0.9.0"
"package": "SWXMLHash",
"repositoryURL": "https://github.com/drmohundro/SWXMLHash.git",
"state": {
"branch": null,
"revision": "4d0f62f561458cbe1f732171e625f03195151b60",
"version": "7.0.1"
}
},
{
"identity" : "swxmlhash",
"kind" : "remoteSourceControl",
"location" : "https://github.com/drmohundro/SWXMLHash.git",
"state" : {
"revision" : "4d0f62f561458cbe1f732171e625f03195151b60",
"version" : "7.0.1"
"package": "Yams",
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "01835dc202670b5bb90d07f3eae41867e9ed29f6",
"version": "5.0.1"
}
}
]
},
{
"identity" : "yams",
"kind" : "remoteSourceControl",
"location" : "https://github.com/jpsim/Yams.git",
"state" : {
"revision" : "f47ba4838c30dbd59998a4e4c87ab620ff959e8a",
"version" : "5.0.5"
}
}
],
"version" : 2
"version": 1
}

View File

@ -1,6 +1,23 @@
// swift-tools-version:5.7
// swift-tools-version:5.5
import PackageDescription
#if os(macOS)
private let addCryptoSwift = false
#else
private let addCryptoSwift = true
#endif
let frameworkDependencies: [Target.Dependency] = [
.product(name: "IDEUtils", package: "SwiftSyntax"),
.product(name: "SourceKittenFramework", package: "SourceKitten"),
.product(name: "SwiftSyntax", package: "SwiftSyntax"),
.product(name: "SwiftSyntaxBuilder", package: "SwiftSyntax"),
.product(name: "SwiftParser", package: "SwiftSyntax"),
.product(name: "SwiftOperators", package: "SwiftSyntax"),
"Yams",
]
+ (addCryptoSwift ? ["CryptoSwift"] : [])
let package = Package(
name: "SwiftLint",
platforms: [.macOS(.v12)],
@ -10,21 +27,19 @@ let package = Package(
.plugin(name: "SwiftLintPlugin", targets: ["SwiftLintPlugin"])
],
dependencies: [
.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(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", .revision("1c4f45b54825ed5192c31d495686ac5b57efff70")),
.package(url: "https://github.com/jpsim/SourceKitten.git", .revision("a9e6df65d8e31e0fa6e8a05ffe40ecd54a645871")),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"),
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
.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"))
],
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.2.0")
] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.6.0"))] : []),
targets: [
.plugin(
name: "SwiftLintPlugin",
capability: .buildTool(),
dependencies: [
.target(name: "SwiftLintBinary", condition: .when(platforms: [.macOS])),
.target(name: "swiftlint", condition: .when(platforms: [.linux]))
.target(name: "swiftlint")
]
),
.executableTarget(
@ -42,38 +57,10 @@ let package = Package(
"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: [
"SwiftLintBuiltInRules",
"SwiftLintCore",
"SwiftLintExtraRules"
]
dependencies: frameworkDependencies
),
.target(name: "DyldWarningWorkaround"),
.target(
name: "SwiftLintTestHelpers",
dependencies: [
@ -112,10 +99,5 @@ let package = Package(
"SwiftLintTestHelpers"
]
),
.binaryTarget(
name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "89651e1c87fb62faf076ef785a5b1af7f43570b2b74c6773526e0d5114e0578e"
)
]
)

View File

@ -1,42 +0,0 @@
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

@ -3,55 +3,18 @@ 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(
func createBuildCommands(
context: PackagePlugin.PluginContext,
target: PackagePlugin.Target
) async throws -> [PackagePlugin.Command] {
[
.buildCommand(
displayName: "SwiftLint",
executable: tool.path,
arguments: arguments,
outputFilesDirectory: outputFilesDirectory
executable: try context.tool(named: "swiftlint").path,
arguments: [
"lint",
"--cache-path", "\(context.pluginWorkDirectory)"
]
)
]
}
@ -61,16 +24,20 @@ struct SwiftLintPlugin: BuildToolPlugin {
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")
func createBuildCommands(
context: XcodePluginContext,
target: XcodeTarget
) throws -> [Command] {
[
.buildCommand(
displayName: "SwiftLint",
executable: try context.tool(named: "swiftlint").path,
arguments: [
"lint",
"--cache-path", "\(context.pluginWorkDirectory)"
]
)
]
}
}
#endif

View File

@ -1,6 +1,6 @@
# 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 [Kodeco's Swift Style Guide](https://github.com/kodecocodes/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 [Ray Wenderlich's Swift Style Guide](https://github.com/raywenderlich/swift-style-guide).
SwiftLint hooks into [Clang](http://clang.llvm.org) and
[SourceKit](http://www.jpsim.com/uncovering-sourcekit) to use the
@ -64,25 +64,19 @@ You can also build and install from source by cloning this project and running
### Using Bazel
Put this in your `MODULE.bazel`:
```bzl
bazel_dep(name = "swiftlint", version = "0.50.4", repo_name = "SwiftLint")
```
Or put this in your `WORKSPACE`:
Put this in your `WORKSPACE`:
<details>
<summary>WORKSPACE</summary>
```bzl
```python
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
http_archive(
name = "build_bazel_rules_apple",
sha256 = "f94e6dddf74739ef5cb30f000e13a2a613f6ebfa5e63588305a71fce8a8a9911",
url = "https://github.com/bazelbuild/rules_apple/releases/download/1.1.3/rules_apple.1.1.3.tar.gz",
sha256 = "36072d4f3614d309d6a703da0dfe48684ec4c65a89611aeb9590b45af7a3e592",
url = "https://github.com/bazelbuild/rules_apple/releases/download/1.0.1/rules_apple.1.0.1.tar.gz",
)
load(
@ -158,10 +152,7 @@ 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
if [[ "$(uname -m)" == arm64 ]]; then
export PATH="/opt/homebrew/bin:$PATH"
fi
export PATH="$PATH:/opt/homebrew/bin"
if which swiftlint > /dev/null; then
swiftlint
else
@ -203,7 +194,7 @@ there is currently no way to pass any additional options to the SwiftLint execut
#### Xcode
You can integrate SwiftLint as an Xcode Build Tool Plug-in if you're working
You can integrate SwiftLint as a 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
@ -215,15 +206,6 @@ 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
@ -239,6 +221,14 @@ Add SwiftLint to a target using the `plugins` parameter.
),
```
### AppCode
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 `⌥⏎`.
### Visual Studio Code
To integrate SwiftLint with [vscode](https://code.visualstudio.com), install the
@ -308,7 +298,6 @@ 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
@ -376,21 +365,12 @@ Once [installed](https://pre-commit.com/#install), add this to the
```yaml
repos:
- repo: https://github.com/realm/SwiftLint
rev: 0.50.3
rev: 0.44.0
hooks:
- id: swiftlint
```
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
```
Adjust `rev` to the SwiftLint version of your choice.
## Rules
@ -401,7 +381,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/SwiftLintBuiltInRules/Rules](https://github.com/realm/SwiftLint/tree/main/Source/SwiftLintBuiltInRules/Rules)
You can also check [Source/SwiftLintFramework/Rules](https://github.com/realm/SwiftLint/tree/main/Source/SwiftLintFramework/Rules)
directory to see their implementation.
### Opt-In Rules
@ -479,9 +459,7 @@ 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. The
special `all` identifier will enable all opt in linter rules, except the ones
listed in `disabled_rules`.
* `opt_in_rules`: Enable rules that are not part of the default set.
* `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
@ -503,9 +481,6 @@ 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`.
@ -514,9 +489,8 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
- Source/ExcludedFolder
- Source/ExcludedFile.swift
- Source/*/ExcludedFile.swift # Exclude files with a wildcard
# If true, SwiftLint will not fail if no lintable files are found.
allow_zero_lintable_files: false
analyzer_rules: # Rules run by `swiftlint analyze`
- explicit_self
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
@ -550,29 +524,13 @@ identifier_name:
- id
- URL
- GlobalAPIKey
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging, summary)
reporter: "xcode" # reporter type (xcode, json, csv, checkstyle, codeclimate, junit, html, emoji, sonarqube, markdown, github-actions-logging)
```
You can also use environment variables in your configuration file,
by using `${SOME_VARIABLE}` in a string.
### 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
#### Defining Custom Rules
You can define custom regex-based rules in your configuration file using the
following syntax:
@ -637,9 +595,6 @@ 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,6 +1,6 @@
# SwiftLint
SwiftLint 是一个用于强制检查 Swift 代码风格和规定的一个工具,基本上以 [Kodeco's Swift 代码风格指南](https://github.com/kodecocodes/swift-style-guide)为基础。
SwiftLint 是一个用于强制检查 Swift 代码风格和规定的一个工具,基本上以 [Ray Wenderlich's Swift 代码风格指南](https://github.com/raywenderlich/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) 来表示源代码文件的更多精确结果。
@ -167,7 +167,7 @@ SwiftLint 已经包含了超过 75 条规则,并且我们希望 Swift 社区
你可以在 [Rule Directory](https://realm.github.io/SwiftLint/rule-directory.html) 找到规则的更新列表和更多信息。
你也可以检视 [Source/SwiftLintBuiltInRules/Rules](Source/SwiftLintBuiltInRules/Rules) 目录来查看它们的实现。
你也可以检视 [Source/SwiftLintFramework/Rules](Source/SwiftLintFramework/Rules) 目录来查看它们的实现。
`opt_in_rules` 默认是关闭的(即,你需要在你的配置文件中明确地打开它们)。

View File

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

View File

@ -7,8 +7,19 @@ 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. 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`
`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 bazel tarball & SHA-256 signature, 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`
1. Celebrate. :tada:

View File

@ -1,15 +0,0 @@
#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

@ -1,19 +0,0 @@
// 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

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

View File

@ -1,142 +0,0 @@
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,71 +0,0 @@
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

@ -1,42 +0,0 @@
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,129 +0,0 @@
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

@ -1,117 +0,0 @@
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

@ -1,106 +0,0 @@
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

@ -1,252 +0,0 @@
import SwiftSyntax
struct StrictFilePrivateRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "strict_fileprivate",
name: "Strict Fileprivate",
description: "`fileprivate` should be avoided",
kind: .idiomatic,
nonTriggeringExamples: [
Example("extension String {}"),
Example("private extension String {}"),
Example("""
public
extension String {
var i: Int { 1 }
}
"""),
Example("""
private enum E {
func f() {}
}
"""),
Example("""
public struct S {
internal let i: Int
}
"""),
Example("""
open class C {
private func f() {}
}
"""),
Example("""
internal actor A {}
"""),
Example("""
struct S1: P {
fileprivate let i = 2, j = 1
}
struct S2: P {
fileprivate var (k, l) = (1, 3)
}
protocol P {
var j: Int { get }
var l: Int { get }
}
""", excludeFromDocumentation: true),
Example("""
class C: P<Int> {
fileprivate func f() {}
}
protocol P<T> {
func f()
}
""", excludeFromDocumentation: true)
] + ["actor", "class", "enum", "extension", "struct"].map { type in
Example("""
\(type) T: P<Int> {
fileprivate func f() {}
fileprivate let i = 3
public fileprivate(set) var l = 3
}
protocol P<T> {
func f()
var i: Int { get }
var l: Int { get set }
}
""", excludeFromDocumentation: true)
},
triggeringExamples: [
Example("""
fileprivate class C {
fileprivate func f() {}
}
"""),
Example("""
fileprivate extension String {
fileprivate var isSomething: Bool { self == "something" }
}
"""),
Example("""
fileprivate actor A {
fileprivate let i = 1
}
"""),
Example("""
fileprivate struct C {
fileprivate(set) var myInt = 4
}
"""),
Example("""
struct Outter {
struct Inter {
fileprivate struct Inner {}
}
}
"""),
Example("""
fileprivate func f() {}
""", excludeFromDocumentation: true)
] + ["actor", "class", "enum", "extension", "struct"].map { type in
Example("""
\(type) T: P<Int> {
fileprivate func f() {}
fileprivate func g() {}
fileprivate let i = 2
public fileprivate(set) var j: Int { 1 }
fileprivate let a = 3, b = 4
public fileprivate(set) var k = 2
}
protocol P<T> {
func f()
var i: Int { get }
var k: Int { get }
}
protocol Q {
func g()
var j: Int { get }
}
""", excludeFromDocumentation: true)
}
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate, file: file.syntaxTree)
}
}
private enum ProtocolRequirementType: Equatable {
case method(String)
case getter(String)
case setter(String)
}
private extension StrictFilePrivateRule {
final class ProtocolCollector: ViolationsSyntaxVisitor {
private(set) var protocols = [String: [ProtocolRequirementType]]()
private var currentProtocolName: String = ""
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .allExcept(ProtocolDeclSyntax.self) }
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
currentProtocolName = node.identifier.text
return .visitChildren
}
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
protocols[currentProtocolName, default: []].append(.method(node.identifier.text))
return .skipChildren
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
for binding in node.bindings {
guard let name = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text,
case .accessors(let accessors) = binding.accessor else {
continue
}
if accessors.specifiesGetAccessor {
protocols[currentProtocolName, default: []].append(.getter(name))
}
if accessors.specifiesSetAccessor {
protocols[currentProtocolName, default: []].append(.setter(name))
}
}
return .skipChildren
}
}
final class Visitor: ViolationsSyntaxVisitor {
private let file: SourceFileSyntax
init(viewMode: SyntaxTreeViewMode, file: SourceFileSyntax) {
self.file = file
super.init(viewMode: viewMode)
}
private lazy var protocols = {
ProtocolCollector(viewMode: .sourceAccurate).walk(tree: file, handler: \.protocols)
}()
override func visitPost(_ node: DeclModifierSyntax) {
guard node.name.tokenKind == .keyword(.fileprivate), let grandparent = node.parent?.parent else {
return
}
guard grandparent.is(FunctionDeclSyntax.self) || grandparent.is(VariableDeclSyntax.self) else {
violations.append(node.positionAfterSkippingLeadingTrivia)
return
}
let protocolMethodNames = implementedTypesInDecl(of: node).flatMap { protocols[$0, default: []] }
if let funcDecl = grandparent.as(FunctionDeclSyntax.self),
protocolMethodNames.contains(.method(funcDecl.identifier.text)) {
return
}
if let varDecl = grandparent.as(VariableDeclSyntax.self) {
let isSpecificForSetter = node.detail?.detail.tokenKind == .identifier("set")
let firstImplementingProtocol = varDecl.bindings
.flatMap { binding in
let pattern = binding.pattern
if let name = pattern.as(IdentifierPatternSyntax.self)?.identifier.text {
return [name]
}
if let tuple = pattern.as(TuplePatternSyntax.self) {
return tuple.elements.compactMap {
$0.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
}
}
return []
}
.first {
protocolMethodNames.contains(isSpecificForSetter ? .setter($0) : .getter($0))
}
if firstImplementingProtocol != nil {
return
}
}
violations.append(node.positionAfterSkippingLeadingTrivia)
}
private func implementedTypesInDecl(of node: SyntaxProtocol?) -> [String] {
guard let node else {
queuedFatalError("Given node is nil. That should not happen.")
}
if node.is(SourceFileSyntax.self) {
return []
}
if let actorDecl = node.as(ActorDeclSyntax.self) {
return actorDecl.inheritanceClause.inheritedTypeNames
}
if let classDecl = node.as(ClassDeclSyntax.self) {
return classDecl.inheritanceClause.inheritedTypeNames
}
if let enumDecl = node.as(EnumDeclSyntax.self) {
return enumDecl.inheritanceClause.inheritedTypeNames
}
if let extensionDecl = node.as(ExtensionDeclSyntax.self) {
return extensionDecl.inheritanceClause.inheritedTypeNames
}
if let structDecl = node.as(StructDeclSyntax.self) {
return structDecl.inheritanceClause.inheritedTypeNames
}
return implementedTypesInDecl(of: node.parent)
}
}
}
private extension TypeInheritanceClauseSyntax? {
var inheritedTypeNames: [String] {
self?.inheritedTypeCollection.compactMap { $0.typeName.as(SimpleTypeIdentifierSyntax.self)?.name.text } ?? []
}
}

View File

@ -1,162 +0,0 @@
import SwiftOperators
import SwiftSyntax
struct XCTSpecificMatcherRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = XCTSpecificMatcherConfiguration()
static let description = RuleDescription(
identifier: "xct_specific_matcher",
name: "XCTest Specific Matcher",
description: "Prefer specific XCTest matchers over `XCTAssertEqual` and `XCTAssertNotEqual`.",
kind: .idiomatic,
nonTriggeringExamples: XCTSpecificMatcherRuleExamples.nonTriggeringExamples,
triggeringExamples: XCTSpecificMatcherRuleExamples.triggeringExamples
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(configuration: configuration)
}
}
private extension XCTSpecificMatcherRule {
final class Visitor: ViolationsSyntaxVisitor {
let configuration: XCTSpecificMatcherConfiguration
init(configuration: XCTSpecificMatcherConfiguration) {
self.configuration = configuration
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: FunctionCallExprSyntax) {
if configuration.matchers.contains(.twoArgumentAsserts),
let suggestion = TwoArgsXCTAssert.violations(in: node) {
violations.append(ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: "Prefer the specific matcher '\(suggestion)' instead"
))
} else if configuration.matchers.contains(.oneArgumentAsserts),
let suggestion = OneArgXCTAssert.violations(in: node) {
violations.append(ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: "Prefer the specific matcher '\(suggestion)' instead"
))
}
}
}
}
private enum OneArgXCTAssert: String {
case assert = "XCTAssert"
case `true` = "XCTAssertTrue"
case `false` = "XCTAssertFalse"
private enum Comparison: String {
case equal = "=="
case unequal = "!="
}
private func suggestion(for comparisonOperator: Comparison) -> String {
switch (self, comparisonOperator) {
case (.assert, .equal): return "XCTAssertEqual"
case (.true, .equal): return "XCTAssertEqual"
case (.assert, .unequal): return "XCTAssertNotEqual"
case (.true, .unequal): return "XCTAssertNotEqual"
case (.false, .equal): return "XCTAssertNotEqual"
case (.false, .unequal): return "XCTAssertEqual"
}
}
static func violations(in node: FunctionCallExprSyntax) -> String? {
guard let name = node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text,
let matcher = Self(rawValue: name),
let argument = node.argumentList.first?.expression.as(SequenceExprSyntax.self),
let folded = try? OperatorTable.standardOperators.foldSingle(argument),
let operatorExpr = folded.as(InfixOperatorExprSyntax.self),
let binOp = operatorExpr.operatorOperand.as(BinaryOperatorExprSyntax.self),
let kind = Comparison(rawValue: binOp.operatorToken.text),
accept(operand: operatorExpr.leftOperand), accept(operand: operatorExpr.rightOperand) else {
return nil
}
return matcher.suggestion(for: kind)
}
private static func accept(operand: ExprSyntax) -> Bool {
// Check if the expression could be a type object like `String.self`. Note, however, that `1.self`
// is also valid Swift. There is no way to be sure here.
if operand.as(MemberAccessExprSyntax.self)?.name.text == "self" {
return false
}
if operand.as(TupleExprSyntax.self)?.elementList.count ?? 0 > 1 {
return false
}
return true
}
}
private enum TwoArgsXCTAssert: String {
case equal = "XCTAssertEqual"
case notEqual = "XCTAssertNotEqual"
private static let protectedArguments: Set<String> = [
"false", "true", "nil"
]
private func suggestion(for protectedArgument: String, hasOptional: Bool) -> String? {
switch (self, protectedArgument, hasOptional) {
case (.equal, "true", false): return "XCTAssertTrue"
case (.equal, "false", false): return "XCTAssertFalse"
case (.equal, "nil", _): return "XCTAssertNil"
case (.notEqual, "true", false): return "XCTAssertFalse"
case (.notEqual, "false", false): return "XCTAssertTrue"
case (.notEqual, "nil", _): return "XCTAssertNotNil"
default: return nil
}
}
static func violations(in node: FunctionCallExprSyntax) -> String? {
guard let name = node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text,
let matcher = Self(rawValue: name) else {
return nil
}
//
// - Gets the first two arguments and creates an array where the protected
// word is the first one (if any).
//
// Examples:
//
// - XCTAssertEqual(foo, true) -> [true, foo]
// - XCTAssertEqual(true, foo) -> [true, foo]
// - XCTAssertEqual(foo, true, "toto") -> [true, foo]
// - XCTAssertEqual(1, 2, accuracy: 0.1, "toto") -> [1, 2]
//
let arguments = node.argumentList
.prefix(2)
.map { $0.expression.trimmedDescription }
.sorted { arg1, _ -> Bool in
return protectedArguments.contains(arg1)
}
//
// - Checks if the number of arguments is two (otherwise there's no need to continue).
// - Checks if the first argument is a protected word (otherwise there's no need to continue).
// - Gets the suggestion for the given protected word (taking in consideration the presence of
// optionals.
//
// Examples:
//
// - equal, [true, foo.bar] -> XCTAssertTrue
// - equal, [true, foo?.bar] -> no violation
// - equal, [nil, foo.bar] -> XCTAssertNil
// - equal, [nil, foo?.bar] -> XCTAssertNil
// - equal, [1, 2] -> no violation
//
guard arguments.count == 2,
let argument = arguments.first, protectedArguments.contains(argument),
let hasOptional = arguments.last?.contains("?"),
let suggestedMatcher = matcher.suggestion(for: argument, hasOptional: hasOptional) else {
return nil
}
return suggestedMatcher
}
}

View File

@ -1,194 +0,0 @@
struct BlanketDisableCommandRule: ConfigurationProviderRule {
var configuration = BlanketDisableCommandConfiguration()
static let description = RuleDescription(
identifier: "blanket_disable_command",
name: "Blanket Disable Command",
description: "swiftlint:disable commands should be re-enabled before the end of the file",
kind: .lint,
nonTriggeringExamples: [
Example("""
// swiftlint:disable unused_import
// swiftlint:enable unused_import
"""),
Example("""
// swiftlint:disable unused_import unused_declaration
// swiftlint:enable unused_import
// swiftlint:enable unused_declaration
"""),
Example("// swiftlint:disable:this unused_import"),
Example("// swiftlint:disable:next unused_import"),
Example("// swiftlint:disable:previous unused_import")
],
triggeringExamples: [
Example("// swiftlint:disable ↓unused_import"),
Example("""
// swiftlint:disable unused_import unused_declaration
// swiftlint:enable unused_import
"""),
Example("""
// swiftlint:disable unused_import
// swiftlint:disable unused_import
// swiftlint:enable unused_import
"""),
Example("""
// swiftlint:enable unused_import
""")
].skipWrappingInCommentTests().skipDisableCommandTests()
)
func validate(file: SwiftLintFile) -> [StyleViolation] {
var violations: [StyleViolation] = []
var ruleIdentifierToCommandMap: [RuleIdentifier: Command] = [:]
var disabledRuleIdentifiers: Set<RuleIdentifier> = []
for command in file.commands {
if command.action == .disable {
violations += validateAlreadyDisabledRules(
for: command,
in: file,
disabledRuleIdentifiers: disabledRuleIdentifiers
)
}
if command.action == .enable {
violations += validateAlreadyEnabledRules(
for: command,
in: file,
disabledRuleIdentifiers: disabledRuleIdentifiers
)
}
if command.modifier != nil {
continue
}
if command.action == .disable {
disabledRuleIdentifiers.formUnion(command.ruleIdentifiers)
command.ruleIdentifiers.forEach { ruleIdentifierToCommandMap[$0] = command }
}
if command.action == .enable {
disabledRuleIdentifiers.subtract(command.ruleIdentifiers)
command.ruleIdentifiers.forEach { ruleIdentifierToCommandMap.removeValue(forKey: $0) }
}
}
violations += validateBlanketDisables(
in: file,
disabledRuleIdentifiers: disabledRuleIdentifiers,
ruleIdentifierToCommandMap: ruleIdentifierToCommandMap
)
violations += validateAlwaysBlanketDisable(file: file)
return violations
}
private func violation(
for command: Command,
ruleIdentifier: RuleIdentifier,
in file: SwiftLintFile,
reason: String
) -> StyleViolation {
violation(for: command, ruleIdentifier: ruleIdentifier.stringRepresentation, in: file, reason: reason)
}
private func violation(
for command: Command,
ruleIdentifier: String,
in file: SwiftLintFile,
reason: String
) -> StyleViolation {
StyleViolation(
ruleDescription: Self.description,
severity: configuration.severity,
location: command.location(of: ruleIdentifier, in: file),
reason: reason
)
}
private func validateAlreadyDisabledRules(
for command: Command,
in file: SwiftLintFile,
disabledRuleIdentifiers: Set<RuleIdentifier>
) -> [StyleViolation] {
let alreadyDisabledRuleIdentifiers = command.ruleIdentifiers.intersection(disabledRuleIdentifiers)
return alreadyDisabledRuleIdentifiers.map {
let reason = "The disabled '\($0.stringRepresentation)' rule was already disabled"
return violation(for: command, ruleIdentifier: $0, in: file, reason: reason)
}
}
private func validateAlreadyEnabledRules(
for command: Command,
in file: SwiftLintFile,
disabledRuleIdentifiers: Set<RuleIdentifier>
) -> [StyleViolation] {
let notDisabledRuleIdentifiers = command.ruleIdentifiers.subtracting(disabledRuleIdentifiers)
return notDisabledRuleIdentifiers.map {
let reason = "The enabled '\($0.stringRepresentation)' rule was not disabled"
return violation(for: command, ruleIdentifier: $0, in: file, reason: reason)
}
}
private func validateBlanketDisables(
in file: SwiftLintFile,
disabledRuleIdentifiers: Set<RuleIdentifier>,
ruleIdentifierToCommandMap: [RuleIdentifier: Command]
) -> [StyleViolation] {
let allowedRuleIdentifiers = configuration.allowedRuleIdentifiers.union(
configuration.alwaysBlanketDisableRuleIdentifiers
)
return disabledRuleIdentifiers.compactMap { disabledRuleIdentifier in
if allowedRuleIdentifiers.contains(disabledRuleIdentifier.stringRepresentation) {
return nil
}
if let command = ruleIdentifierToCommandMap[disabledRuleIdentifier] {
let reason = "The disabled '\(disabledRuleIdentifier.stringRepresentation)' rule " +
"should be re-enabled before the end of the file"
return violation(for: command, ruleIdentifier: disabledRuleIdentifier, in: file, reason: reason)
}
return nil
}
}
private func validateAlwaysBlanketDisable(file: SwiftLintFile) -> [StyleViolation] {
var violations: [StyleViolation] = []
guard configuration.alwaysBlanketDisableRuleIdentifiers.isEmpty == false else {
return []
}
for command in file.commands {
let ruleIdentifiers: Set<String> = Set(command.ruleIdentifiers.map { $0.stringRepresentation })
let intersection = ruleIdentifiers.intersection(configuration.alwaysBlanketDisableRuleIdentifiers)
if command.action == .enable {
violations.append(contentsOf: intersection.map {
let reason = "The '\($0)' rule applies to the whole file and thus doesn't need to be re-enabled"
return violation(for: command, ruleIdentifier: $0, in: file, reason: reason)
})
} else if command.modifier != nil {
violations.append(contentsOf: intersection.map {
let reason = "The '\($0)' rule applies to the whole file and thus cannot be disabled locally " +
"with 'previous', 'this' or 'next'"
return violation(for: command, ruleIdentifier: $0, in: file, reason: reason)
})
}
}
return violations
}
}
private extension Command {
func location(of ruleIdentifier: String, in file: SwiftLintFile) -> Location {
var location = character
if line > 0, line <= file.lines.count {
let line = file.lines[line - 1].content
if let ruleIdentifierIndex = line.range(of: ruleIdentifier)?.lowerBound {
location = line.distance(from: line.startIndex, to: ruleIdentifierIndex) + 1
}
}
return Location(file: file.file.path, line: line, character: location)
}
}

View File

@ -1,184 +0,0 @@
import SwiftSyntax
struct CompilerProtocolInitRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "compiler_protocol_init",
name: "Compiler Protocol Init",
description: "The initializers declared in compiler protocols such as `ExpressibleByArrayLiteral` " +
"shouldn't be called directly.",
kind: .lint,
nonTriggeringExamples: [
Example("let set: Set<Int> = [1, 2]\n"),
Example("let set = Set(array)\n")
],
triggeringExamples: [
Example("let set = ↓Set(arrayLiteral: 1, 2)\n"),
Example("let set = ↓Set (arrayLiteral: 1, 2)\n"),
Example("let set = ↓Set.init(arrayLiteral: 1, 2)\n"),
Example("let set = ↓Set.init(arrayLiteral : 1, 2)\n")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension CompilerProtocolInitRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
guard node.trailingClosure == nil else {
return
}
let arguments = node.argumentList.compactMap(\.label)
guard ExpressibleByCompiler.possibleNumberOfArguments.contains(arguments.count) else {
return
}
guard let name = node.functionName, ExpressibleByCompiler.allInitNames.contains(name) else {
return
}
let argumentsNames = arguments.map(\.text)
for compilerProtocol in ExpressibleByCompiler.allProtocols {
guard compilerProtocol.initCallNames.contains(name),
compilerProtocol.match(arguments: argumentsNames) else {
continue
}
violations.append(ReasonedRuleViolation(
position: node.positionAfterSkippingLeadingTrivia,
reason: "Initializers declared in compiler protocol \(compilerProtocol.protocolName) " +
"shouldn't be called directly"
))
return
}
}
}
}
private extension FunctionCallExprSyntax {
// doing this instead of calling `.description` as it's faster
var functionName: String? {
if let expr = calledExpression.as(IdentifierExprSyntax.self) {
return expr.identifier.text
} else if let expr = calledExpression.as(MemberAccessExprSyntax.self),
let base = expr.base?.as(IdentifierExprSyntax.self) {
return base.identifier.text + "." + expr.name.text
}
// we don't care about other possible expressions as they wouldn't match the calls we're interested in
return nil
}
}
private struct ExpressibleByCompiler {
let protocolName: String
let initCallNames: Set<String>
private let arguments: Set<[String]>
private init(protocolName: String, types: Set<String>, arguments: Set<[String]>) {
self.protocolName = protocolName
self.arguments = arguments
initCallNames = Set(types.flatMap { [$0, "\($0).init"] })
}
static let allProtocols = [byArrayLiteral, byNilLiteral, byBooleanLiteral,
byFloatLiteral, byIntegerLiteral, byUnicodeScalarLiteral,
byExtendedGraphemeClusterLiteral, byStringLiteral,
byStringInterpolation, byDictionaryLiteral]
static let possibleNumberOfArguments: Set<Int> = {
allProtocols.reduce(into: Set<Int>()) { partialResult, entry in
partialResult.insert(entry.arguments.count)
}
}()
static let allInitNames: Set<String> = {
allProtocols.reduce(into: Set<String>()) { partialResult, entry in
partialResult.formUnion(entry.initCallNames)
}
}()
func match(arguments: [String]) -> Bool {
return self.arguments.contains(arguments)
}
private static let byArrayLiteral: ExpressibleByCompiler = {
let types: Set = [
"Array",
"ArraySlice",
"ContiguousArray",
"IndexPath",
"NSArray",
"NSCountedSet",
"NSMutableArray",
"NSMutableOrderedSet",
"NSMutableSet",
"NSOrderedSet",
"NSSet",
"SBElementArray",
"Set",
"IndexSet"
]
return Self(protocolName: "ExpressibleByArrayLiteral", types: types, arguments: [["arrayLiteral"]])
}()
private static let byNilLiteral = Self(
protocolName: "ExpressibleByNilLiteral",
types: ["Optional"],
arguments: [["nilLiteral"]]
)
private static let byBooleanLiteral = Self(
protocolName: "ExpressibleByBooleanLiteral",
types: ["Bool", "NSDecimalNumber", "NSNumber", "ObjCBool"],
arguments: [["booleanLiteral"]]
)
private static let byFloatLiteral = Self(
protocolName: "ExpressibleByFloatLiteral",
types: ["Decimal", "NSDecimalNumber", "NSNumber"],
arguments: [["floatLiteral"]]
)
private static let byIntegerLiteral = Self(
protocolName: "ExpressibleByIntegerLiteral",
types: ["Decimal", "Double", "Float", "Float80", "NSDecimalNumber", "NSNumber"],
arguments: [["integerLiteral"]]
)
private static let byUnicodeScalarLiteral = Self(
protocolName: "ExpressibleByUnicodeScalarLiteral",
types: ["StaticString", "String", "UnicodeScalar"],
arguments: [["unicodeScalarLiteral"]]
)
private static let byExtendedGraphemeClusterLiteral = Self(
protocolName: "ExpressibleByExtendedGraphemeClusterLiteral",
types: ["Character", "StaticString", "String"],
arguments: [["extendedGraphemeClusterLiteral"]]
)
private static let byStringLiteral = Self(
protocolName: "ExpressibleByStringLiteral",
types: ["CSLocalizedString", "NSMutableString", "NSString", "Selector", "StaticString", "String"],
arguments: [["stringLiteral"]]
)
private static let byStringInterpolation = Self(
protocolName: "ExpressibleByStringInterpolation",
types: ["String"],
arguments: [["stringInterpolation"], ["stringInterpolationSegment"]]
)
private static let byDictionaryLiteral = Self(
protocolName: "ExpressibleByDictionaryLiteral",
types: ["Dictionary", "DictionaryLiteral", "NSDictionary", "NSMutableDictionary"],
arguments: [["dictionaryLiteral"]]
)
}

View File

@ -1,219 +0,0 @@
import SwiftSyntax
struct DuplicateConditionsRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.error)
static let description = RuleDescription(
identifier: "duplicate_conditions",
name: "Duplicate Conditions",
description: "Duplicate sets of conditions in the same branch instruction should be avoided",
kind: .lint,
nonTriggeringExamples: [
Example("""
if x < 5 {
foo()
} else if y == "s" {
bar()
}
"""),
Example("""
if x < 5 {
foo()
}
if x < 5 {
bar()
}
"""),
Example("""
if x < 5, y == "s" {
foo()
} else if x < 5 {
bar()
}
"""),
Example("""
switch x {
case \"a\":
foo()
bar()
}
"""),
Example("""
switch x {
case \"a\" where y == "s":
foo()
case \"a\" where y == "t":
bar()
}
"""),
Example("""
if let x = maybeAbc {
foo()
} else if let x = maybePqr {
bar()
}
"""),
Example("""
if let x = maybeAbc, let z = x.maybeY {
foo()
} else if let x = maybePqr, let z = x.maybeY {
bar()
}
"""),
Example("""
if case .p = x {
foo()
} else if case .q = x {
bar()
}
"""),
Example("""
if true {
if true { foo() }
}
""")
],
triggeringExamples: [
Example("""
if x < 5 {
foo()
} else if y == "s" {
bar()
} else if x < 5 {
baz()
}
"""),
Example("""
if z {
if x < 5 {
foo()
} else if y == "s" {
bar()
} else if x < 5 {
baz()
}
}
"""),
Example("""
if x < 5, y == "s" {
foo()
} else if x < 10 {
bar()
} else if y == "s", x < 5 {
baz()
}
"""),
Example("""
switch x {
case \"a\", \"b\":
foo()
case \"c\", ↓\"a\":
bar()
}
"""),
Example("""
switch x {
case \"a\" where y == "s":
foo()
case \"a\" where y == "s":
bar()
}
"""),
Example("""
if let xyz = maybeXyz {
foo()
} else if let xyz = maybeXyz {
bar()
}
"""),
Example("""
if let x = maybeAbc, let z = x.maybeY {
foo()
} else if let x = maybeAbc, let z = x.maybeY {
bar()
}
"""),
Example("""
if #available(macOS 10.15, *) {
foo()
} else if #available(macOS 10.15, *) {
bar()
}
"""),
Example("""
if case .p = x {
foo()
} else if case .p = x {
bar()
}
"""),
Example("""
if x < 5 {}
else if x < 5 {}
else if x < 5 {}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension DuplicateConditionsRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: IfExprSyntax) {
if node.parent?.is(IfExprSyntax.self) == true {
// We can skip these cases - they will be picked up when we visit the top level `if`
return
}
var maybeCurr: IfExprSyntax? = node
var statementChain: [IfExprSyntax] = []
while let curr = maybeCurr {
statementChain.append(curr)
maybeCurr = curr.elseBody?.as(IfExprSyntax.self)
}
let positionsByConditions = statementChain
.reduce(into: [Set<String>: [AbsolutePosition]]()) { acc, elt in
let conditions = elt.conditions.map { $0.condition.trimmedDescription }
let location = elt.conditions.positionAfterSkippingLeadingTrivia
acc[Set(conditions), default: []].append(location)
}
addViolations(Array(positionsByConditions.values))
}
override func visitPost(_ node: SwitchCaseListSyntax) {
let switchCases = node.compactMap { $0.as(SwitchCaseSyntax.self) }
let positionsByCondition = switchCases
.reduce(into: [String: [AbsolutePosition]]()) { acc, elt in
// Defaults don't have a condition to worry about
guard case let .case(caseLabel) = elt.label else { return }
for caseItem in caseLabel.caseItems {
let pattern = caseItem
.pattern
.trimmedDescription
let whereClause = caseItem
.whereClause?
.trimmedDescription
?? ""
let location = caseItem.positionAfterSkippingLeadingTrivia
acc[pattern + whereClause, default: []].append(location)
}
}
addViolations(Array(positionsByCondition.values))
}
private func addViolations(_ positionsByCondition: [[AbsolutePosition]]) {
let duplicatedPositions = positionsByCondition
.filter { $0.count > 1 }
.flatMap { $0 }
violations.append(contentsOf: duplicatedPositions)
}
}
}

View File

@ -1,116 +0,0 @@
import SwiftSyntax
// TODO: [12/23/2024] Remove deprecation warning after ~2 years.
private let warnDeprecatedOnceImpl: Void = {
Issue.ruleDeprecated(ruleID: InertDeferRule.description.identifier).print()
}()
private func warnDeprecatedOnce() {
_ = warnDeprecatedOnceImpl
}
struct InertDeferRule: ConfigurationProviderRule, SwiftSyntaxRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "inert_defer",
name: "Inert Defer",
description: "If defer is at the end of its parent scope, it will be executed right where it is anyway",
kind: .lint,
nonTriggeringExamples: [
Example("""
func example3() {
defer { /* deferred code */ }
print("other code")
}
"""),
Example("""
func example4() {
if condition {
defer { /* deferred code */ }
print("other code")
}
}
"""),
Example("""
func f() {
#if os(macOS)
defer { print(2) }
#else
defer { print(3) }
#endif
print(1)
}
""", excludeFromDocumentation: true)
],
triggeringExamples: [
Example("""
func example0() {
defer { /* deferred code */ }
}
"""),
Example("""
func example1() {
defer { /* deferred code */ }
// comment
}
"""),
Example("""
func example2() {
if condition {
defer { /* deferred code */ }
// comment
}
}
"""),
Example("""
func f(arg: Int) {
if arg == 1 {
defer { print(2) }
// a comment
} else {
defer { print(3) }
}
print(1)
#if os(macOS)
defer { print(4) }
#else
defer { print(5) }
#endif
}
""", excludeFromDocumentation: true)
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
warnDeprecatedOnce()
return Visitor(viewMode: .sourceAccurate)
}
}
private extension InertDeferRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: DeferStmtSyntax) {
guard node.isLastStatement else {
return
}
if let ifConfigClause = node.parent?.parent?.parent?.as(IfConfigClauseSyntax.self),
ifConfigClause.parent?.parent?.isLastStatement == false {
return
}
violations.append(node.deferKeyword.positionAfterSkippingLeadingTrivia)
}
}
}
private extension SyntaxProtocol {
var isLastStatement: Bool {
if let codeBlockItem = parent?.as(CodeBlockItemSyntax.self),
let codeBlockList = codeBlockItem.parent?.as(CodeBlockItemListSyntax.self) {
return codeBlockList.last == codeBlockItem
}
return false
}
}

View File

@ -1,44 +0,0 @@
struct InvalidSwiftLintCommandRule: ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "invalid_swiftlint_command",
name: "Invalid SwiftLint Command",
description: "swiftlint command does not have a valid action or modifier",
kind: .lint,
nonTriggeringExamples: [
Example("// swiftlint:disable unused_import"),
Example("// swiftlint:enable unused_import"),
Example("// swiftlint:disable:next unused_import"),
Example("// swiftlint:disable:previous unused_import"),
Example("// swiftlint:disable:this unused_import")
],
triggeringExamples: [
Example("// swiftlint:"),
Example("// swiftlint: "),
Example("// swiftlint::"),
Example("// swiftlint:: "),
Example("// swiftlint:disable"),
Example("// swiftlint:dissable unused_import"),
Example("// swiftlint:enaaaable unused_import"),
Example("// swiftlint:disable:nxt unused_import"),
Example("// swiftlint:enable:prevus unused_import"),
Example("// swiftlint:enable:ths unused_import"),
Example("// swiftlint:enable"),
Example("// swiftlint:enable:"),
Example("// swiftlint:enable: "),
Example("// swiftlint:disable: unused_import")
].skipWrappingInCommentTests()
)
func validate(file: SwiftLintFile) -> [StyleViolation] {
file.invalidCommands.map {
let location = Location(file: file.path, line: $0.line, character: $0.character)
return StyleViolation(
ruleDescription: Self.description,
severity: configuration.severity,
location: location
)
}
}
}

View File

@ -1,70 +0,0 @@
import SwiftIDEUtils
import SwiftSyntax
struct LocalDocCommentRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "local_doc_comment",
name: "Local Doc Comment",
description: "Prefer regular comments over doc comments in local scopes",
kind: .lint,
nonTriggeringExamples: [
Example("""
func foo() {
// Local scope documentation should use normal comments.
print("foo")
}
"""),
Example("""
/// My great property
var myGreatProperty: String!
"""),
Example("""
/// Look here for more info: https://github.com.
var myGreatProperty: String!
"""),
Example("""
/// Look here for more info:
/// https://github.com.
var myGreatProperty: String!
""")
],
triggeringExamples: [
Example("""
func foo() {
/// Docstring inside a function declaration
print("foo")
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(classifications: file.syntaxClassifications.filter { $0.kind != .none })
}
}
private extension LocalDocCommentRule {
final class Visitor: ViolationsSyntaxVisitor {
private let docCommentRanges: [ByteSourceRange]
init(classifications: [SyntaxClassifiedRange]) {
self.docCommentRanges = classifications
.filter { $0.kind == .docLineComment || $0.kind == .docBlockComment }
.map(\.range)
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: FunctionDeclSyntax) {
guard let body = node.body else {
return
}
let violatingRange = docCommentRanges.first { $0.intersects(body.byteRange) }
if let violatingRange {
violations.append(AbsolutePosition(utf8Offset: violatingRange.offset))
}
}
}
}

View File

@ -1,79 +0,0 @@
import SwiftSyntax
struct NSLocalizedStringKeyRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "nslocalizedstring_key",
name: "NSLocalizedString Key",
description: "Static strings should be used as key/comment" +
" in NSLocalizedString in order for genstrings to work",
kind: .lint,
nonTriggeringExamples: [
Example("NSLocalizedString(\"key\", comment: \"\")"),
Example("NSLocalizedString(\"key\" + \"2\", comment: \"\")"),
Example("NSLocalizedString(\"key\", comment: \"comment\")"),
Example("""
NSLocalizedString("This is a multi-" +
"line string", comment: "")
"""),
Example("""
let format = NSLocalizedString("%@, %@.", comment: "Accessibility label for a post in the post list." +
" The parameters are the title, and date respectively." +
" For example, \"Let it Go, 1 hour ago.\"")
""")
],
triggeringExamples: [
Example("NSLocalizedString(↓method(), comment: \"\")"),
Example("NSLocalizedString(↓\"key_\\(param)\", comment: \"\")"),
Example("NSLocalizedString(\"key\", comment: ↓\"comment with \\(param)\")"),
Example("NSLocalizedString(↓\"key_\\(param)\", comment: ↓method())")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension NSLocalizedStringKeyRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
guard node.calledExpression.as(IdentifierExprSyntax.self)?.identifier.text == "NSLocalizedString" else {
return
}
if let keyArgument = node.argumentList.first(where: { $0.label == nil })?.expression,
keyArgument.hasViolation {
violations.append(keyArgument.positionAfterSkippingLeadingTrivia)
}
if let commentArgument = node.argumentList.first(where: { $0.label?.text == "comment" })?.expression,
commentArgument.hasViolation {
violations.append(commentArgument.positionAfterSkippingLeadingTrivia)
}
}
}
}
private extension ExprSyntax {
var hasViolation: Bool {
if let strExpr = self.as(StringLiteralExprSyntax.self) {
return strExpr.segments.contains { segment in
!segment.is(StringSegmentSyntax.self)
}
}
if let sequenceExpr = self.as(SequenceExprSyntax.self) {
return sequenceExpr.elements.contains { expr in
if expr.is(BinaryOperatorExprSyntax.self) {
return false
}
return expr.hasViolation
}
}
return true
}
}

View File

@ -1,92 +0,0 @@
import Foundation
import SourceKittenFramework
import SwiftIDEUtils
struct PeriodSpacingRule: SourceKitFreeRule, ConfigurationProviderRule, OptInRule, SubstitutionCorrectableRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "period_spacing",
name: "Period Spacing",
description: "Periods should not be followed by more than one space",
kind: .style,
nonTriggeringExamples: [
Example("let pi = 3.2"),
Example("let pi = Double.pi"),
Example("let pi = Double. pi"),
Example("let pi = Double. pi"),
Example("// A. Single."),
Example("/// - code: Identifier of the error. Integer."),
Example("""
// value: Multiline.
// Comment.
"""),
Example("""
/**
Sentence ended in period.
- Sentence 2 new line characters after.
**/
""")
],
triggeringExamples: [
Example("/* Only god knows why. ↓ This symbol does nothing. */", testWrappingInComment: false),
Example("// Only god knows why. ↓ This symbol does nothing.", testWrappingInComment: false),
Example("// Single. Double. ↓ End.", testWrappingInComment: false),
Example("// Single. Double. ↓ Triple. ↓ End.", testWrappingInComment: false),
Example("// Triple. ↓ Quad. ↓ End.", testWrappingInComment: false),
Example("/// - code: Identifier of the error. ↓ Integer.", testWrappingInComment: false)
],
corrections: [
Example("/* Why. ↓ Symbol does nothing. */"): Example("/* Why. Symbol does nothing. */"),
Example("// Why. ↓ Symbol does nothing."): Example("// Why. Symbol does nothing."),
Example("// Single. Double. ↓ End."): Example("// Single. Double. End."),
Example("// Single. Double. ↓ Triple. ↓ End."): Example("// Single. Double. Triple. End."),
Example("// Triple. ↓ Quad. ↓ End."): Example("// Triple. Quad. End."),
Example("/// - code: Identifier. ↓ Integer."): Example("/// - code: Identifier. Integer.")
]
)
func violationRanges(in file: SwiftLintFile) -> [NSRange] {
// Find all comment tokens in the file and regex search them for violations
file.syntaxClassifications
.filter(\.kind.isComment)
.map { $0.range.toSourceKittenByteRange() }
.compactMap { (range: ByteRange) -> [NSRange]? in
return file.stringView
.substringWithByteRange(range)
.map(StringView.init)
.map { commentBody in
// Look for a period followed by two or more whitespaces but not new line or carriage returns
return regex(#"\.[^\S\r\n]{2,}"#)
.matches(in: commentBody)
.compactMap { result in
// Set the location to start from the second whitespace till the last one.
return file.stringView.byteRangeToNSRange(
ByteRange(
// Safe to mix NSRange offsets with byte offsets here because the
// regex can't contain multi-byte characters
location: ByteCount(range.lowerBound.value + result.range.lowerBound + 2),
length: ByteCount(result.range.length.advanced(by: -2))
)
)
}
}
}
.flatMap { $0 }
}
func validate(file: SwiftLintFile) -> [StyleViolation] {
return violationRanges(in: file).map { range in
StyleViolation(
ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, characterOffset: range.location)
)
}
}
func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
return (violationRange, "")
}
}

View File

@ -1,128 +0,0 @@
import SourceKittenFramework
struct QuickDiscouragedCallRule: OptInRule, ConfigurationProviderRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "quick_discouraged_call",
name: "Quick Discouraged Call",
description: "Discouraged call inside 'describe' and/or 'context' block.",
kind: .lint,
nonTriggeringExamples: QuickDiscouragedCallRuleExamples.nonTriggeringExamples,
triggeringExamples: QuickDiscouragedCallRuleExamples.triggeringExamples
)
func validate(file: SwiftLintFile) -> [StyleViolation] {
let dict = file.structureDictionary
let testClasses = dict.substructure.filter {
return $0.inheritedTypes.isNotEmpty &&
$0.declarationKind == .class
}
let specDeclarations = testClasses.flatMap { classDict in
return classDict.substructure.filter {
return $0.name == "spec()" && $0.enclosedVarParameters.isEmpty &&
$0.declarationKind == .functionMethodInstance &&
$0.enclosedSwiftAttributes.contains(.override)
}
}
return specDeclarations.flatMap {
validate(file: file, dictionary: $0)
}
}
private func validate(file: SwiftLintFile, dictionary: SourceKittenDictionary) -> [StyleViolation] {
return dictionary.traverseDepthFirst { subDict in
guard let kind = subDict.expressionKind else { return nil }
return validate(file: file, kind: kind, dictionary: subDict)
}
}
private func validate(file: SwiftLintFile,
kind: SwiftExpressionKind,
dictionary: SourceKittenDictionary) -> [StyleViolation] {
// is it a call to a restricted method?
guard
kind == .call,
let name = dictionary.name,
let kindName = QuickCallKind(rawValue: name),
QuickCallKind.restrictiveKinds.contains(kindName)
else { return [] }
return violationOffsets(in: dictionary.enclosedArguments).map {
StyleViolation(ruleDescription: Self.description,
severity: configuration.severity,
location: Location(file: file, byteOffset: $0),
reason: "Discouraged call inside a '\(name)' block")
}
}
private func violationOffsets(in substructure: [SourceKittenDictionary]) -> [ByteCount] {
return substructure.flatMap { dictionary -> [ByteCount] in
let substructure = dictionary.substructure.flatMap { dict -> [SourceKittenDictionary] in
if dict.expressionKind == .closure {
return dict.substructure
} else {
return [dict]
}
}
return substructure.flatMap(toViolationOffsets)
}
}
private func toViolationOffsets(dictionary: SourceKittenDictionary) -> [ByteCount] {
guard
dictionary.kind != nil,
let offset = dictionary.offset
else { return [] }
if dictionary.expressionKind == .call,
let name = dictionary.name, QuickCallKind(rawValue: name) == nil {
return [offset]
}
guard dictionary.expressionKind != .call else { return [] }
return dictionary.substructure.compactMap(toViolationOffset)
}
private func toViolationOffset(dictionary: SourceKittenDictionary) -> ByteCount? {
guard
let name = dictionary.name,
let offset = dictionary.offset,
dictionary.expressionKind == .call,
QuickCallKind(rawValue: name) == nil
else { return nil }
return offset
}
}
private enum QuickCallKind: String {
case describe
case context
case sharedExamples
case itBehavesLike
case aroundEach
case beforeEach
case justBeforeEach
case beforeSuite
case afterEach
case afterSuite
case it // swiftlint:disable:this identifier_name
case pending
case xdescribe
case xcontext
case xit
case xitBehavesLike
case fdescribe
case fcontext
case fit
case fitBehavesLike
static let restrictiveKinds: Set<QuickCallKind> = [
.describe, .fdescribe, .xdescribe, .context, .fcontext, .xcontext, .sharedExamples
]
}

View File

@ -1,344 +0,0 @@
import SwiftSyntax
struct UnhandledThrowingTaskRule: ConfigurationProviderRule, SwiftSyntaxRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.error)
static let description = RuleDescription(
identifier: "unhandled_throwing_task",
name: "Unhandled Throwing Task",
description: """
Errors thrown inside this task are not handled, which may be unexpected. \
Handle errors inside the task, or use `try await` to access the Tasks value and handle errors. \
See this forum thread for more details: \
https://forums.swift.org/t/task-initializer-with-throwing-closure-swallows-error/56066
""",
kind: .lint,
nonTriggeringExamples: [
Example("""
Task<Void, Never> {
try await myThrowingFunction()
}
"""),
Example("""
Task {
try? await myThrowingFunction()
}
"""),
Example("""
Task {
try! await myThrowingFunction()
}
"""),
Example("""
Task<Void, String> {
let text = try myThrowingFunction()
return text
}
"""),
Example("""
Task {
do {
try myThrowingFunction()
} catch let e {
print(e)
}
}
"""),
Example("""
func someFunction() throws {
Task {
anotherFunction()
do {
try myThrowingFunction()
} catch {
print(error)
}
}
try something()
}
"""),
Example("""
let task = Task {
try await myThrowingFunction()
}
"""),
Example("""
var task = Task {
try await myThrowingFunction()
}
"""),
Example("""
try await Task {
try await myThrowingFunction()
}.value
"""),
Example("""
executor.task = Task {
try await isolatedOpen(.init(executor.asUnownedSerialExecutor()))
}
"""),
Example("""
let result = await Task {
throw CancellationError()
}.result
"""),
Example("""
func makeTask() -> Task<String, Error> {
return Task {
try await someThrowingFunction()
}
}
"""),
Example("""
func makeTask() -> Task<String, Error> {
// Implicit return
Task {
try await someThrowingFunction()
}
}
"""),
Example("""
Task {
return Result {
try someThrowingFunc()
}
}
""")
],
triggeringExamples: [
Example("""
Task {
try await myThrowingFunction()
}
"""),
Example("""
Task {
let text = try myThrowingFunction()
return text
}
"""),
Example("""
Task {
do {
try myThrowingFunction()
}
}
"""),
Example("""
Task {
do {
try myThrowingFunction()
} catch let e as FooError {
print(e)
}
}
"""),
Example("""
Task {
do {
throw FooError.bar
}
}
"""),
Example("""
Task {
throw FooError.bar
}
"""),
Example("""
Task<_, _> {
throw FooError.bar
}
"""),
Example("""
Task<Void,_> {
throw FooError.bar
}
"""),
Example("""
Task {
do {
try foo()
} catch {
try bar()
}
}
"""),
Example("""
Task {
do {
try foo()
} catch {
throw BarError()
}
}
"""),
Example("""
func doTask() {
Task {
try await someThrowingFunction()
}
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension UnhandledThrowingTaskRule {
final class Visitor: ViolationsSyntaxVisitor {
override func visitPost(_ node: FunctionCallExprSyntax) {
if node.hasViolation {
violations.append(node.calledExpression.positionAfterSkippingLeadingTrivia)
}
}
}
}
private extension FunctionCallExprSyntax {
var hasViolation: Bool {
isTaskWithImplicitErrorType &&
doesThrow &&
!(isAssigned || isValueOrResultAccessed || isReturnValue)
}
var isTaskWithImplicitErrorType: Bool {
if let typeIdentifier = calledExpression.as(IdentifierExprSyntax.self),
typeIdentifier.identifier.text == "Task" {
return true
}
if let specializedExpression = calledExpression.as(SpecializeExprSyntax.self),
let typeIdentifier = specializedExpression.expression.as(IdentifierExprSyntax.self),
typeIdentifier.identifier.text == "Task",
let lastGeneric = specializedExpression.genericArgumentClause
.arguments.last?.argumentType.as(SimpleTypeIdentifierSyntax.self),
lastGeneric.typeName == "_" {
return true
}
return false
}
var isAssigned: Bool {
guard let parent else {
return false
}
if parent.is(InitializerClauseSyntax.self) {
return true
}
if let list = parent.as(ExprListSyntax.self),
list.contains(where: { $0.is(AssignmentExprSyntax.self) }) {
return true
}
return false
}
var isValueOrResultAccessed: Bool {
guard let parent = parent?.as(MemberAccessExprSyntax.self) else {
return false
}
return parent.name.text == "value" || parent.name.text == "result"
}
var doesThrow: Bool {
ThrowsVisitor(viewMode: .sourceAccurate)
.walk(tree: self, handler: \.doesThrow)
}
}
/// If the `doesThrow` property is true after visiting, then this node throws an error that is "unhandled."
/// Try statements inside a `do` with a `catch` that handles all errors will not be marked as throwing.
private final class ThrowsVisitor: SyntaxVisitor {
var doesThrow = false
override func visit(_ node: DoStmtSyntax) -> SyntaxVisitorContinueKind {
// No need to continue traversing if we already throw.
if doesThrow {
return .skipChildren
}
// If there are no catch clauses, visit children to see if there are any try expressions.
guard let lastCatchClause = node.catchClauses?.last else {
return .visitChildren
}
let catchItems = lastCatchClause.catchItems ?? []
// If we have a value binding pattern, only an IdentifierPatternSyntax will catch
// any error; if it's not an IdentifierPatternSyntax, we need to visit children.
if let pattern = catchItems.last?.pattern?.as(ValueBindingPatternSyntax.self),
!pattern.valuePattern.is(IdentifierPatternSyntax.self) {
return .visitChildren
}
// Check the catch clause tree for unhandled throws.
if ThrowsVisitor(viewMode: .sourceAccurate).walk(tree: lastCatchClause, handler: \.doesThrow) {
doesThrow = true
}
// We don't need to visit children of the `do` node, since all errors are handled by the catch.
return .skipChildren
}
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
// No need to continue traversing if we already throw.
if doesThrow {
return .skipChildren
}
// Result initializers with trailing closures handle thrown errors.
if let typeIdentifier = node.calledExpression.as(IdentifierExprSyntax.self),
typeIdentifier.identifier.text == "Result",
node.trailingClosure != nil {
return .skipChildren
}
return .visitChildren
}
override func visitPost(_ node: TryExprSyntax) {
if node.questionOrExclamationMark == nil {
doesThrow = true
}
}
override func visitPost(_ node: ThrowStmtSyntax) {
doesThrow = true
}
}
private extension SyntaxProtocol {
var isExplicitReturnValue: Bool {
parent?.is(ReturnStmtSyntax.self) == true
}
var isImplicitReturnValue: Bool {
// 4th parent: FunctionDecl
// 3rd parent: | CodeBlock
// 2nd parent: | CodeBlockItemList
// 1st parent: | CodeBlockItem
// Current node: | FunctionDeclSyntax
guard
let parentFunctionDecl = parent?.parent?.parent?.parent?.as(FunctionDeclSyntax.self),
parentFunctionDecl.body?.statements.count == 1,
parentFunctionDecl.signature.output != nil
else {
return false
}
return true
}
var isReturnValue: Bool {
isExplicitReturnValue || isImplicitReturnValue
}
}

View File

@ -1,14 +0,0 @@
struct FunctionBodyLengthRule: SwiftSyntaxRule, ConfigurationProviderRule {
var configuration = SeverityLevelsConfiguration<Self>(warning: 50, error: 100)
static let description = RuleDescription(
identifier: "function_body_length",
name: "Function Body Length",
description: "Function bodies should not span too many lines",
kind: .metrics
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
BodyLengthRuleVisitor(kind: .function, file: file, configuration: configuration)
}
}

View File

@ -1,41 +0,0 @@
struct BlanketDisableCommandConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = BlanketDisableCommandRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var allowedRuleIdentifiers: Set<String> = [
"file_header",
"file_length",
"file_name",
"file_name_no_space",
"single_test_class"
]
private(set) var alwaysBlanketDisableRuleIdentifiers: Set<String> = []
var consoleDescription: String {
"severity: \(severityConfiguration.consoleDescription)" +
", allowed_rules: \(allowedRuleIdentifiers.sorted())" +
", always_blanket_disable: \(alwaysBlanketDisableRuleIdentifiers.sorted())"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let allowedRuleIdentifiers = configuration["allowed_rules"] as? [String] {
self.allowedRuleIdentifiers = Set(allowedRuleIdentifiers)
}
if let alwaysBlanketDisableRuleIdentifiers = configuration["always_blanket_disable"] as? [String] {
self.alwaysBlanketDisableRuleIdentifiers = Set(alwaysBlanketDisableRuleIdentifiers)
}
}
var severity: ViolationSeverity {
severityConfiguration.severity
}
}

View File

@ -1,24 +0,0 @@
struct CollectionAlignmentConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = CollectionAlignmentRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var alignColons = false
init() {}
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", align_colons: \(alignColons)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
alignColons = configuration["align_colons"] as? Bool ?? false
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,22 +0,0 @@
struct ConditionalReturnsOnNewlineConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ConditionalReturnsOnNewlineRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var ifOnly = false
var consoleDescription: String {
return ["severity: \(severityConfiguration.consoleDescription)", "if_only: \(ifOnly)"].joined(separator: ", ")
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
ifOnly = configuration["if_only"] as? Bool ?? false
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,45 +0,0 @@
struct ExplicitTypeInterfaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ExplicitTypeInterfaceRule
enum VariableKind: String, CaseIterable {
case instance
case local
case `static`
case `class`
static let all = Set(allCases)
}
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var allowedKinds = VariableKind.all
private(set) var allowRedundancy = false
var consoleDescription: String {
let excludedKinds = VariableKind.all.subtracting(allowedKinds).map(\.rawValue).sorted()
return "severity: \(severityConfiguration.consoleDescription)" +
", excluded: \(excludedKinds)" +
", allow_redundancy: \(allowRedundancy)"
}
init() {}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
for (key, value) in configuration {
switch (key, value) {
case ("severity", let severityString as String):
try severityConfiguration.apply(configuration: severityString)
case ("excluded", let excludedStrings as [String]):
allowedKinds.subtract(excludedStrings.compactMap(VariableKind.init(rawValue:)))
case ("allow_redundancy", let allowRedundancy as Bool):
self.allowRedundancy = allowRedundancy
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
}

View File

@ -1,39 +0,0 @@
struct FileNameConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = FileNameRule
var consoleDescription: String {
return "(severity) \(severityConfiguration.consoleDescription), " +
"excluded: \(excluded.sorted()), " +
"prefix_pattern: \(prefixPattern), " +
"suffix_pattern: \(suffixPattern), " +
"nested_type_separator: \(nestedTypeSeparator)"
}
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
private(set) var excluded = Set<String>(["main.swift", "LinuxMain.swift"])
private(set) var prefixPattern = ""
private(set) var suffixPattern = "\\+.*"
private(set) var nestedTypeSeparator = "."
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severity = configurationDict["severity"] {
try severityConfiguration.apply(configuration: severity)
}
if let excluded = [String].array(of: configurationDict["excluded"]) {
self.excluded = Set(excluded)
}
if let prefixPattern = configurationDict["prefix_pattern"] as? String {
self.prefixPattern = prefixPattern
}
if let suffixPattern = configurationDict["suffix_pattern"] as? String {
self.suffixPattern = suffixPattern
}
if let nestedTypeSeparator = configurationDict["nested_type_separator"] as? String {
self.nestedTypeSeparator = nestedTypeSeparator
}
}
}

View File

@ -1,24 +0,0 @@
struct FileNameNoSpaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = FileNameNoSpaceRule
var consoleDescription: String {
return "(severity) \(severityConfiguration.consoleDescription), " +
"excluded: \(excluded.sorted())"
}
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
private(set) var excluded = Set<String>()
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severity = configurationDict["severity"] {
try severityConfiguration.apply(configuration: severity)
}
if let excluded = [String].array(of: configurationDict["excluded"]) {
self.excluded = Set(excluded)
}
}
}

View File

@ -1,22 +0,0 @@
struct ForWhereConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ForWhereRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var allowForAsFilter = false
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", allow_for_as_filter: \(allowForAsFilter)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
allowForAsFilter = configuration["allow_for_as_filter"] as? Bool ?? false
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,40 +0,0 @@
struct ImplicitlyUnwrappedOptionalConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ImplicitlyUnwrappedOptionalRule
// swiftlint:disable:next type_name
enum ImplicitlyUnwrappedOptionalModeConfiguration: String {
case all = "all"
case allExceptIBOutlets = "all_except_iboutlets"
init(value: Any) throws {
if let string = (value as? String)?.lowercased(),
let value = Self(rawValue: string) {
self = value
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
private(set) var mode = ImplicitlyUnwrappedOptionalModeConfiguration.allExceptIBOutlets
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" +
", mode: \(mode.rawValue)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let modeString = configuration["mode"] {
try mode = ImplicitlyUnwrappedOptionalModeConfiguration(value: modeString)
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,43 +0,0 @@
struct IndentationWidthConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = IndentationWidthRule
var consoleDescription: String {
return "severity: \("severity: \(severityConfiguration.consoleDescription)"), "
+ "indentation_width: \(indentationWidth), "
+ "include_comments: \(includeComments), "
+ "include_compiler_directives: \(includeCompilerDirectives)"
+ "include_multiline_strings: \(includeMultilineStrings)"
}
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
private(set) var indentationWidth = 4
private(set) var includeComments = true
private(set) var includeCompilerDirectives = true
private(set) var includeMultilineStrings = true
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let config = configurationDict["severity"] {
try severityConfiguration.apply(configuration: config)
}
if let indentationWidth = configurationDict["indentation_width"] as? Int, indentationWidth >= 1 {
self.indentationWidth = indentationWidth
}
if let includeComments = configurationDict["include_comments"] as? Bool {
self.includeComments = includeComments
}
if let includeCompilerDirectives = configurationDict["include_compiler_directives"] as? Bool {
self.includeCompilerDirectives = includeCompilerDirectives
}
if let includeMultilineStrings = configurationDict["include_multiline_strings"] as? Bool {
self.includeMultilineStrings = includeMultilineStrings
}
}
}

View File

@ -1,46 +0,0 @@
import SourceKittenFramework
struct ModifierOrderConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = ModifierOrderRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var preferredModifierOrder: [SwiftDeclarationAttributeKind.ModifierGroup] = [
.override,
.acl,
.setterACL,
.dynamic,
.mutators,
.lazy,
.final,
.required,
.convenience,
.typeMethods,
.owned
]
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)"
+ ", preferred_modifier_order: \(preferredModifierOrder)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let preferredModifierOrder = configuration["preferred_modifier_order"] as? [String] {
self.preferredModifierOrder = try preferredModifierOrder.map {
guard let modifierGroup = SwiftDeclarationAttributeKind.ModifierGroup(rawValue: $0),
modifierGroup != .atPrefixed else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
return modifierGroup
}
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,23 +0,0 @@
struct MultilineParametersConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = MultilineParametersRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var allowsSingleLine = true
var consoleDescription: String {
"severity: \(severityConfiguration.consoleDescription)"
+ ", allowsSingleLine: \(allowsSingleLine)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
allowsSingleLine = configuration["allows_single_line"] as? Bool ?? true
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,108 +0,0 @@
import Foundation
struct NameConfiguration<Parent: Rule>: RuleConfiguration, Equatable {
typealias Severity = SeverityConfiguration<Parent>
typealias SeverityLevels = SeverityLevelsConfiguration<Parent>
typealias StartWithLowercaseConfiguration = ChildOptionSeverityConfiguration<Parent>
var consoleDescription: String {
"(min_length) \(minLength.shortConsoleDescription), " +
"(max_length) \(maxLength.shortConsoleDescription), " +
"excluded: \(excludedRegularExpressions.map { $0.pattern }.sorted()), " +
"allowed_symbols: \(allowedSymbols.sorted()), " +
"validates_start_with_lowercase: \(validatesStartWithLowercase.consoleDescription)"
}
private(set) var minLength: SeverityLevels
private(set) var maxLength: SeverityLevels
private(set) var excludedRegularExpressions: Set<NSRegularExpression>
private(set) var validatesStartWithLowercase: StartWithLowercaseConfiguration
private(set) var allowedSymbols: Set<String>
var minLengthThreshold: Int {
return max(minLength.warning, minLength.error ?? minLength.warning)
}
var maxLengthThreshold: Int {
return min(maxLength.warning, maxLength.error ?? maxLength.warning)
}
var allowedSymbolsAndAlphanumerics: CharacterSet {
CharacterSet(charactersIn: allowedSymbols.joined()).union(.alphanumerics)
}
init(minLengthWarning: Int,
minLengthError: Int,
maxLengthWarning: Int,
maxLengthError: Int,
excluded: [String] = [],
allowedSymbols: [String] = [],
validatesStartWithLowercase: StartWithLowercaseConfiguration = .error) {
minLength = SeverityLevels(warning: minLengthWarning, error: minLengthError)
maxLength = SeverityLevels(warning: maxLengthWarning, error: maxLengthError)
self.excludedRegularExpressions = Set(excluded.compactMap {
try? NSRegularExpression.cached(pattern: "^\($0)$")
})
self.allowedSymbols = Set(allowedSymbols)
self.validatesStartWithLowercase = validatesStartWithLowercase
}
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let minLengthConfiguration = configurationDict["min_length"] {
try minLength.apply(configuration: minLengthConfiguration)
}
if let maxLengthConfiguration = configurationDict["max_length"] {
try maxLength.apply(configuration: maxLengthConfiguration)
}
if let excluded = [String].array(of: configurationDict["excluded"]) {
self.excludedRegularExpressions = Set(excluded.compactMap {
try? NSRegularExpression.cached(pattern: "^\($0)$")
})
}
if let allowedSymbols = [String].array(of: configurationDict["allowed_symbols"]) {
self.allowedSymbols = Set(allowedSymbols)
}
if let validatesStartWithLowercase = configurationDict["validates_start_with_lowercase"] as? String {
try self.validatesStartWithLowercase.apply(configuration: validatesStartWithLowercase)
} else if let validatesStartWithLowercase = configurationDict["validates_start_with_lowercase"] as? Bool {
// TODO: [05/10/2025] Remove deprecation warning after ~2 years.
self.validatesStartWithLowercase = validatesStartWithLowercase ? .error : .off
Issue.genericWarning(
"""
The \"validates_start_with_lowercase\" configuration now expects a severity (warning or \
error). The boolean value 'true' will still enable it as an error. It is now deprecated and will be \
removed in a future release.
"""
).print()
}
}
}
extension NameConfiguration {
func severity(forLength length: Int) -> ViolationSeverity? {
if let minError = minLength.error, length < minError {
return .error
} else if let maxError = maxLength.error, length > maxError {
return .error
} else if length < minLength.warning ||
length > maxLength.warning {
return .warning
}
return nil
}
}
// MARK: - `exclude` option extensions
extension NameConfiguration {
func shouldExclude(name: String) -> Bool {
excludedRegularExpressions.contains {
!$0.matches(in: name, options: [], range: NSRange(name.startIndex..., in: name)).isEmpty
}
}
}

View File

@ -1,22 +0,0 @@
struct PrefixedTopLevelConstantConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = PrefixedTopLevelConstantRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var onlyPrivateMembers = false
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", only_private: \(onlyPrivateMembers)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
onlyPrivateMembers = (configuration["only_private"] as? Bool == true)
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,22 +0,0 @@
struct PrivateOutletConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = PrivateOutletRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var allowPrivateSet = false
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", allow_private_set: \(allowPrivateSet)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
allowPrivateSet = (configuration["allow_private_set"] as? Bool == true)
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,24 +0,0 @@
struct PrivateOverFilePrivateConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = PrivateOverFilePrivateRule
var severityConfiguration = SeverityConfiguration<Parent>(.warning)
var validateExtensions = false
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", validate_extensions: \(validateExtensions)"
}
// MARK: - RuleConfiguration
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
validateExtensions = configuration["validate_extensions"] as? Bool ?? false
}
}

View File

@ -1,37 +0,0 @@
struct SortedImportsConfiguration: RuleConfiguration, Equatable {
typealias Parent = SortedImportsRule
enum SortedImportsGroupingConfiguration: String {
/// Sorts import lines based on any import attributes (e.g. `@testable`, `@_exported`, etc.), followed by a case
/// insensitive comparison of the imported module name.
case attributes
/// Sorts import lines based on a case insensitive comparison of the imported module name.
case names
}
private(set) var severity = SeverityConfiguration<Parent>(.warning)
private(set) var grouping = SortedImportsGroupingConfiguration.names
var consoleDescription: String {
return "severity: \(severity.consoleDescription)"
+ ", grouping: \(grouping)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let rawGrouping = configuration["grouping"] {
guard let rawGrouping = rawGrouping as? String,
let grouping = SortedImportsGroupingConfiguration(rawValue: rawGrouping) else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
self.grouping = grouping
}
if let severityString = configuration["severity"] as? String {
try severity.apply(configuration: severityString)
}
}
}

View File

@ -1,37 +0,0 @@
struct StatementPositionConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = StatementPositionRule
enum StatementModeConfiguration: String {
case `default` = "default"
case uncuddledElse = "uncuddled_else"
init(value: Any) throws {
if let string = (value as? String)?.lowercased(),
let value = Self(rawValue: string) {
self = value
} else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
var consoleDescription: String {
return "(statement_mode) \(statementMode.rawValue), " +
"(severity) \(severityConfiguration.consoleDescription)"
}
private(set) var statementMode = StatementModeConfiguration.default
private(set) var severityConfiguration = SeverityConfiguration<Parent>.warning
mutating func apply(configuration: Any) throws {
guard let configurationDict = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let statementModeConfiguration = configurationDict["statement_mode"] {
try statementMode = StatementModeConfiguration(value: statementModeConfiguration)
}
if let severity = configurationDict["severity"] {
try severityConfiguration.apply(configuration: severity)
}
}
}

View File

@ -1,24 +0,0 @@
struct SwitchCaseAlignmentConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = SwitchCaseAlignmentRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var indentedCases = false
init() {}
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", indented_cases: \(indentedCases)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
indentedCases = configuration["indented_cases"] as? Bool ?? false
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,31 +0,0 @@
struct TestCaseAccessibilityConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TestCaseAccessibilityRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var allowedPrefixes: Set<String> = []
private(set) var testParentClasses: Set<String> = ["QuickSpec", "XCTestCase"]
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" +
", allowed_prefixes: \(allowedPrefixes.sorted())" +
", test_parent_classes: \(testParentClasses.sorted())"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let allowedPrefixes = configuration["allowed_prefixes"] as? [String] {
self.allowedPrefixes = Set(allowedPrefixes)
}
if let extraTestParentClasses = configuration["test_parent_classes"] as? [String] {
self.testParentClasses.formUnion(extraTestParentClasses)
}
}
}

View File

@ -1,26 +0,0 @@
struct TrailingCommaConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TrailingCommaRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var mandatoryComma: Bool
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", mandatory_comma: \(mandatoryComma)"
}
init(mandatoryComma: Bool = false) {
self.mandatoryComma = mandatoryComma
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
mandatoryComma = (configuration["mandatory_comma"] as? Bool == true)
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,26 +0,0 @@
struct TrailingWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = TrailingWhitespaceRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var ignoresEmptyLines = false
private(set) var ignoresComments = true
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" +
", ignores_empty_lines: \(ignoresEmptyLines)" +
", ignores_comments: \(ignoresComments)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
ignoresEmptyLines = (configuration["ignores_empty_lines"] as? Bool == true)
ignoresComments = (configuration["ignores_comments"] as? Bool == true)
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,24 +0,0 @@
struct TypeNameConfiguration: RuleConfiguration, Equatable {
typealias Parent = TypeNameRule
private(set) var nameConfiguration = NameConfiguration<Parent>(minLengthWarning: 3,
minLengthError: 0,
maxLengthWarning: 40,
maxLengthError: 1000)
private(set) var validateProtocols = true
var consoleDescription: String {
return nameConfiguration.consoleDescription + ", validate_protocols: \(validateProtocols)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
try nameConfiguration.apply(configuration: configuration)
if let validateProtocols = configuration["validate_protocols"] as? Bool {
self.validateProtocols = validateProtocols
}
}
}

View File

@ -1,32 +0,0 @@
typealias BalancedXCTestLifecycleConfiguration = UnitTestConfiguration<BalancedXCTestLifecycleRule>
typealias EmptyXCTestMethodConfiguration = UnitTestConfiguration<EmptyXCTestMethodRule>
typealias SingleTestClassConfiguration = UnitTestConfiguration<SingleTestClassRule>
typealias NoMagicNumbersConfiguration = UnitTestConfiguration<NoMagicNumbersRule>
struct UnitTestConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Equatable {
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var testParentClasses: Set<String> = ["QuickSpec", "XCTestCase"]
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" +
", test_parent_classes: \(testParentClasses.sorted())"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let extraTestParentClasses = configuration["test_parent_classes"] as? [String] {
self.testParentClasses.formUnion(extraTestParentClasses)
}
}
var severity: ViolationSeverity {
return severityConfiguration.severity
}
}

View File

@ -1,24 +0,0 @@
struct UnusedOptionalBindingConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = UnusedOptionalBindingRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var ignoreOptionalTry = false
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", ignore_optional_try: \(ignoreOptionalTry)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let ignoreOptionalTry = configuration["ignore_optional_try"] as? Bool {
self.ignoreOptionalTry = ignoreOptionalTry
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,38 +0,0 @@
// swiftlint:disable:next type_name
struct VerticalWhitespaceClosingBracesConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = VerticalWhitespaceClosingBracesRule
private enum ConfigurationKey: String {
case severity = "severity"
case onlyEnforceBeforeTrivialLines = "only_enforce_before_trivial_lines"
}
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var onlyEnforceBeforeTrivialLines = false
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" +
", \(ConfigurationKey.onlyEnforceBeforeTrivialLines.rawValue): \(onlyEnforceBeforeTrivialLines)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
for (string, value) in configuration {
guard let key = ConfigurationKey(rawValue: string) else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
switch (key, value) {
case (.severity, let stringValue as String):
try severityConfiguration.apply(configuration: stringValue)
case (.onlyEnforceBeforeTrivialLines, let boolValue as Bool):
onlyEnforceBeforeTrivialLines = boolValue
default:
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
}
}
}

View File

@ -1,24 +0,0 @@
struct VerticalWhitespaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = VerticalWhitespaceRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var maxEmptyLines = 1
var consoleDescription: String {
return "severity: \(severityConfiguration.consoleDescription)" + ", max_empty_lines: \(maxEmptyLines)"
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let maxEmptyLines = configuration["max_empty_lines"] as? Int {
self.maxEmptyLines = maxEmptyLines
}
if let severityString = configuration["severity"] as? String {
try severityConfiguration.apply(configuration: severityString)
}
}
}

View File

@ -1,37 +0,0 @@
struct XCTSpecificMatcherConfiguration: SeverityBasedRuleConfiguration, Equatable {
typealias Parent = XCTSpecificMatcherRule
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
private(set) var matchers = Set(Matcher.allCases)
enum Matcher: String, Hashable, CaseIterable {
case oneArgumentAsserts = "one-argument-asserts"
case twoArgumentAsserts = "two-argument-asserts"
}
private enum ConfigurationKey: String {
case severity
case matchers
}
var consoleDescription: String {
return [
"severity: \(severityConfiguration.consoleDescription)",
"\(ConfigurationKey.matchers): \(matchers.map(\.rawValue).sorted().joined(separator: ", "))"
].joined(separator: ", ")
}
mutating func apply(configuration: Any) throws {
guard let configuration = configuration as? [String: Any] else {
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
}
if let severityString = configuration[ConfigurationKey.severity.rawValue] as? String {
try severityConfiguration.apply(configuration: severityString)
}
if let matchers = configuration[ConfigurationKey.matchers.rawValue] as? [String] {
self.matchers = Set(matchers.compactMap(Matcher.init(rawValue:)))
}
}
}

View File

@ -1,263 +0,0 @@
import SwiftSyntax
struct DirectReturnRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static var description = RuleDescription(
identifier: "direct_return",
name: "Direct Return",
description: "Directly return the expression instead of assigning it to a variable first",
kind: .style,
nonTriggeringExamples: [
Example("""
func f() -> Int {
let b = 2
let a = 1
return b
}
"""),
Example("""
struct S {
var a: Int {
var b = 1
b = 2
return b
}
}
"""),
Example("""
func f() -> Int {
let b = 2
f()
return b
}
"""),
Example("""
func f() -> Int {
{ i in
let b = 2
return i
}(1)
}
""")
],
triggeringExamples: [
Example("""
func f() -> Int {
let b = 2
return b
}
"""),
Example("""
struct S {
var a: Int {
var b = 1
// comment
return b
}
}
"""),
Example("""
func f() -> Bool {
let a = 1, b = true
return b
}
"""),
Example("""
func f() -> Int {
{ _ in
let b = 2
return b
}(1)
}
"""),
Example("""
func f(i: Int) -> Int {
if i > 1 {
let a = 2
return a
} else {
let b = 2, a = 1
return b
}
}
""")
],
corrections: [
Example("""
func f() -> Int {
let b = 2
return b
}
"""): Example("""
func f() -> Int {
return 2
}
"""),
Example("""
struct S {
var a: Int {
var b = 2 > 1
? f()
: 1_000
// comment
return b
}
func f() -> Int { 1 }
}
"""): Example("""
struct S {
var a: Int {
// comment
return 2 > 1
? f()
: 1_000
}
func f() -> Int { 1 }
}
"""),
Example("""
func f() -> Bool {
let a = 1, b = true
return b
}
"""): Example("""
func f() -> Bool {
let a = 1
return true
}
"""),
Example("""
func f() -> Int {
{ _ in
// A comment
let b = 2
// Another comment
return b
}(1)
}
"""): Example("""
func f() -> Int {
{ _ in
// A comment
// Another comment
return 2
}(1)
}
"""),
Example("""
func f() -> Bool {
let b : Bool = true
return b
}
"""): Example("""
func f() -> Bool {
return true as Bool
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
Rewriter(
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
private class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visitPost(_ statements: CodeBlockItemListSyntax) {
if let (binding, _) = statements.violation {
violations.append(binding.positionAfterSkippingLeadingTrivia)
}
}
}
private extension CodeBlockItemListSyntax {
var violation: (PatternBindingSyntax, ReturnStmtSyntax)? {
guard count >= 2, let last = last?.item,
let returnStmt = last.as(ReturnStmtSyntax.self),
let identifier = returnStmt.expression?.as(IdentifierExprSyntax.self)?.identifier.text,
let varDecl = dropLast().last?.item.as(VariableDeclSyntax.self) else {
return nil
}
let binding = varDecl.bindings.first {
$0.pattern.as(IdentifierPatternSyntax.self)?.identifier.text == identifier
}
if let binding {
return (binding, returnStmt)
}
return nil
}
}
private 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(_ statements: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax {
guard let (binding, returnStmt) = statements.violation,
!binding.isContainedIn(regions: disabledRegions, locationConverter: locationConverter),
let bindingList = binding.parent?.as(PatternBindingListSyntax.self),
let varDecl = bindingList.parent?.as(VariableDeclSyntax.self),
var initExpression = binding.initializer?.value else {
return super.visit(statements)
}
correctionPositions.append(binding.positionAfterSkippingLeadingTrivia)
var newStmtList = Array(statements.dropLast(2))
let newBindingList = bindingList
.filter { $0 != binding }
.enumerated()
.map { index, item in
if index == bindingList.count - 2 {
return item.with(\.trailingComma, nil)
}
return item
}
if let type = binding.typeAnnotation?.type {
initExpression = ExprSyntax(
fromProtocol: AsExprSyntax(
expression: initExpression.trimmed,
asTok: .keyword(.as).with(\.leadingTrivia, .space).with(\.trailingTrivia, .space),
typeName: type.trimmed
)
)
}
if newBindingList.isNotEmpty {
newStmtList.append(CodeBlockItemSyntax(
item: .decl(DeclSyntax(varDecl.with(\.bindings, PatternBindingListSyntax(newBindingList))))
))
newStmtList.append(CodeBlockItemSyntax(
item: .stmt(StmtSyntax(returnStmt.with(\.expression, initExpression)))
))
} else {
let leadingTrivia = varDecl.leadingTrivia.withoutTrailingIndentation +
varDecl.trailingTrivia +
returnStmt.leadingTrivia.withFirstEmptyLineRemoved
newStmtList.append(
CodeBlockItemSyntax(
item: .stmt(
StmtSyntax(
returnStmt
.with(\.expression, initExpression)
.with(\.leadingTrivia, leadingTrivia)
)
)
)
)
}
return super.visit(CodeBlockItemListSyntax(newStmtList))
}
}

View File

@ -1,128 +0,0 @@
import SwiftSyntax
struct MultilineArgumentsRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
var configuration = MultilineArgumentsConfiguration()
static let description = RuleDescription(
identifier: "multiline_arguments",
name: "Multiline Arguments",
description: "Arguments should be either on the same line, or one per line",
kind: .style,
nonTriggeringExamples: MultilineArgumentsRuleExamples.nonTriggeringExamples,
triggeringExamples: MultilineArgumentsRuleExamples.triggeringExamples
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(
onlyEnforceAfterFirstClosureOnFirstLine: configuration.onlyEnforceAfterFirstClosureOnFirstLine,
firstArgumentLocation: configuration.firstArgumentLocation,
locationConverter: file.locationConverter
)
}
}
private extension MultilineArgumentsRule {
final class Visitor: ViolationsSyntaxVisitor {
let onlyEnforceAfterFirstClosureOnFirstLine: Bool
let firstArgumentLocation: MultilineArgumentsConfiguration.FirstArgumentLocation
let locationConverter: SourceLocationConverter
init(onlyEnforceAfterFirstClosureOnFirstLine: Bool,
firstArgumentLocation: MultilineArgumentsConfiguration.FirstArgumentLocation,
locationConverter: SourceLocationConverter) {
self.onlyEnforceAfterFirstClosureOnFirstLine = onlyEnforceAfterFirstClosureOnFirstLine
self.firstArgumentLocation = firstArgumentLocation
self.locationConverter = locationConverter
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: FunctionCallExprSyntax) {
guard node.argumentList.count > 1 else {
return
}
let functionCallPosition = node.calledExpression.positionAfterSkippingLeadingTrivia
let functionCallLine = locationConverter.location(for: functionCallPosition).line
let wrappedArguments: [Argument] = node.argumentList
.enumerated()
.compactMap { idx, argument in
Argument(element: argument, locationConverter: locationConverter, index: idx)
}
var violatingArguments = findViolations(in: wrappedArguments, functionCallLine: functionCallLine)
if onlyEnforceAfterFirstClosureOnFirstLine {
violatingArguments = removeViolationsBeforeFirstClosure(arguments: wrappedArguments,
violations: violatingArguments)
}
violations.append(contentsOf: violatingArguments.map(\.offset))
}
// MARK: - Violation Logic
private func findViolations(in arguments: [Argument],
functionCallLine: Int) -> [Argument] {
var visitedLines = Set<Int>()
if firstArgumentLocation == .sameLine {
visitedLines.insert(functionCallLine)
}
let violations = arguments.compactMap { argument -> Argument? in
let (line, idx) = (argument.line, argument.index)
let (firstVisit, _) = visitedLines.insert(line)
if idx == 0 {
switch firstArgumentLocation {
case .anyLine: return nil
case .nextLine: return line > functionCallLine ? nil : argument
case .sameLine: return line > functionCallLine ? argument : nil
}
} else {
return firstVisit ? nil : argument
}
}
// only report violations if multiline
return visitedLines.count > 1 ? violations : []
}
private func removeViolationsBeforeFirstClosure(arguments: [Argument],
violations: [Argument]) -> [Argument] {
guard let firstClosure = arguments.first(where: { $0.isClosure }),
let firstArgument = arguments.first else {
return violations
}
let violationSlice: ArraySlice<Argument> = violations
.drop { argument in
// drop violations if they precede the first closure,
// if that closure is in the first line
firstArgument.line == firstClosure.line &&
argument.line == firstClosure.line &&
argument.index <= firstClosure.index
}
return Array(violationSlice)
}
}
}
private struct Argument {
let offset: AbsolutePosition
let line: Int
let index: Int
let expression: ExprSyntax
init?(element: TupleExprElementSyntax, locationConverter: SourceLocationConverter, index: Int) {
self.offset = element.positionAfterSkippingLeadingTrivia
self.line = locationConverter.location(for: offset).line
self.index = index
self.expression = element.expression
}
var isClosure: Bool {
expression.is(ClosureExprSyntax.self)
}
}

View File

@ -1,237 +0,0 @@
import SwiftSyntax
struct PreferSelfInStaticReferencesRule: SwiftSyntaxRule, CorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static var description = RuleDescription(
identifier: "prefer_self_in_static_references",
name: "Prefer Self in Static References",
description: "Use `Self` to refer to the surrounding type name",
kind: .style,
nonTriggeringExamples: PreferSelfInStaticReferencesRuleExamples.nonTriggeringExamples,
triggeringExamples: PreferSelfInStaticReferencesRuleExamples.triggeringExamples,
corrections: PreferSelfInStaticReferencesRuleExamples.corrections
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
func correct(file: SwiftLintFile) -> [Correction] {
let ranges = Visitor(viewMode: .sourceAccurate)
.walk(file: file, handler: \.corrections)
.compactMap { file.stringView.NSRange(start: $0.start, end: $0.end) }
.filter { file.ruleEnabled(violatingRange: $0, for: self) != nil }
.reversed()
var corrections = [Correction]()
var contents = file.contents
for range in ranges {
let contentsNSString = contents.bridge()
contents = contentsNSString.replacingCharacters(in: range, with: "Self")
let location = Location(file: file, characterOffset: range.location)
corrections.append(Correction(ruleDescription: Self.description, location: location))
}
file.write(contents)
return corrections
}
}
private class Visitor: ViolationsSyntaxVisitor {
private enum ParentDeclBehavior {
case likeClass(name: String)
case likeStruct(String)
case skipReferences
var parentName: String? {
switch self {
case let .likeClass(name): return name
case let .likeStruct(name): return name
case .skipReferences: return nil
}
}
}
private enum VariableDeclBehavior {
case handleReferences
case skipReferences
}
private var parentDeclScopes = Stack<ParentDeclBehavior>()
private var variableDeclScopes = Stack<VariableDeclBehavior>()
private(set) var corrections = [(start: AbsolutePosition, end: AbsolutePosition)]()
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.push(.likeClass(name: node.identifier.text))
return .skipChildren
}
override func visitPost(_ node: ActorDeclSyntax) {
parentDeclScopes.pop()
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.push(.likeClass(name: node.identifier.text))
return .visitChildren
}
override func visitPost(_ node: ClassDeclSyntax) {
parentDeclScopes.pop()
}
override func visit(_ node: CodeBlockSyntax) -> SyntaxVisitorContinueKind {
variableDeclScopes.push(.handleReferences)
return .visitChildren
}
override func visitPost(_ node: CodeBlockSyntax) {
variableDeclScopes.pop()
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.push(.likeStruct(node.identifier.text))
return .visitChildren
}
override func visitPost(_ node: EnumDeclSyntax) {
parentDeclScopes.pop()
}
override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.push(.skipReferences)
return .visitChildren
}
override func visitPost(_ node: ExtensionDeclSyntax) {
parentDeclScopes.pop()
}
override func visit(_ node: MemberAccessExprSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.peek() {
if node.name.tokenKind == .keyword(.self) {
return .skipChildren
}
}
return .visitChildren
}
override func visitPost(_ node: IdentifierExprSyntax) {
guard let parent = node.parent, !parent.is(SpecializeExprSyntax.self) else {
return
}
if parent.is(FunctionCallExprSyntax.self), case .likeClass = parentDeclScopes.peek() {
return
}
addViolation(on: node.identifier)
}
override func visit(_ node: MemberDeclBlockSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.peek() {
variableDeclScopes.push(.skipReferences)
} else {
variableDeclScopes.push(.handleReferences)
}
return .visitChildren
}
override func visitPost(_ node: MemberDeclBlockSyntax) {
variableDeclScopes.pop()
}
override func visit(_ node: MacroExpansionExprSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.peek(), case .identifier("selector") = node.macro.tokenKind {
return .visitChildren
}
return .skipChildren
}
override func visit(_ node: ParameterClauseSyntax) -> SyntaxVisitorContinueKind {
if case .likeStruct = parentDeclScopes.peek() {
return .visitChildren
}
return .skipChildren
}
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.push(.skipReferences)
return .skipChildren
}
override func visitPost(_ node: ProtocolDeclSyntax) {
parentDeclScopes.pop()
}
override func visit(_ node: ReturnClauseSyntax) -> SyntaxVisitorContinueKind {
if case .likeStruct = parentDeclScopes.peek() {
return .visitChildren
}
return .skipChildren
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
parentDeclScopes.push(.likeStruct(node.identifier.text))
return .visitChildren
}
override func visitPost(_ node: StructDeclSyntax) {
parentDeclScopes.pop()
}
override func visitPost(_ node: SimpleTypeIdentifierSyntax) {
guard let parent = node.parent else {
return
}
if case .likeClass = parentDeclScopes.peek(), parent.is(GenericArgumentSyntax.self) {
// Type is a generic parameter in a class.
return
}
if node.genericArguments == nil {
// Type is specialized.
addViolation(on: node.name)
}
}
override func visit(_ node: TypealiasDeclSyntax) -> SyntaxVisitorContinueKind {
if case .likeClass = parentDeclScopes.peek() {
return .skipChildren
}
return .visitChildren
}
override func visit(_ node: TypeAnnotationSyntax) -> SyntaxVisitorContinueKind {
guard case .likeStruct = parentDeclScopes.peek() else {
return .skipChildren
}
if let varDecl = node.parent?.parent?.parent?.as(VariableDeclSyntax.self) {
if varDecl.parent?.is(CodeBlockItemSyntax.self) == true // Local variable declaration
|| varDecl.bindings.onlyElement?.accessor != nil // Computed property
|| !node.type.is(SimpleTypeIdentifierSyntax.self) // Complex or collection type
{
return .visitChildren
}
}
return .skipChildren
}
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
if node.bindings.onlyElement?.accessor != nil {
// Variable declaration is a computed property.
return .visitChildren
}
if case .handleReferences = variableDeclScopes.peek() {
return .visitChildren
}
return .skipChildren
}
private func addViolation(on node: TokenSyntax) {
if let parentName = parentDeclScopes.peek()?.parentName, node.tokenKind == .identifier(parentName) {
violations.append(node.positionAfterSkippingLeadingTrivia)
corrections.append(
(start: node.positionAfterSkippingLeadingTrivia, end: node.endPositionBeforeTrailingTrivia)
)
}
}
}

View File

@ -1,232 +0,0 @@
enum PreferSelfInStaticReferencesRuleExamples {
static let nonTriggeringExamples = [
Example("""
class C {
static let primes = [2, 3, 5, 7]
func isPrime(i: Int) -> Bool { Self.primes.contains(i) }
"""),
Example("""
struct T {
static let i = 0
}
struct S {
static let i = 0
}
extension T {
static let j = S.i + T.i
static let k = { T.j }()
}
"""),
Example("""
class `Self` {
static let i = 0
func f() -> Int { Self.i }
}
"""),
Example("""
class C {
static private(set) var i = 0, j = C.i
static let k = { C.i }()
let h = C.i
@GreaterThan(C.j) var k: Int
}
""", excludeFromDocumentation: true),
Example("""
struct S {
struct T {
struct R {
static let i = 3
}
}
struct R {
static let j = S.T.R.i
}
static let j = Self.T.R.i + Self.R.j
let h = Self.T.R.i + Self.R.j
}
""", excludeFromDocumentation: true),
Example("""
class C {
static let s = 2
func f(i: Int = C.s) -> Int {
func g(@GreaterEqualThan(C.s) j: Int = C.s) -> Int { j }
return i + Self.s
}
func g() -> Any { C.self }
}
""", excludeFromDocumentation: true),
Example("""
struct Record<T> {
static func get() -> Record<T> { Record<T>() }
}
""", excludeFromDocumentation: true),
Example("""
@objc class C: NSObject {
@objc var s = ""
@objc func f() { _ = #keyPath(C.s) }
}
""", excludeFromDocumentation: true),
Example("""
class C<T> {
let i = 1
let c: C = C()
func f(c: C) -> KeyPath<C, Int> { \\Self.i }
}
""", excludeFromDocumentation: true)
]
static let triggeringExamples = [
Example("""
final class CheckCellView: NSTableCellView {
@IBOutlet var checkButton: NSButton!
override func awakeFromNib() {
checkButton.action = #selector(CheckCellView.check(_:))
}
@objc func check(_ button: AnyObject?) {}
}
"""),
Example("""
class C {
struct S {
static let i = 2
let h = S.i
}
static let i = 1
let h = C.i
var j: Int { C.i }
func f() -> Int { C.i + h }
}
"""),
Example("""
class C {
func f() {
_ = [C]()
_ = [Int: C]()
}
}
"""),
Example("""
struct S {
let j: Int
static let i = 1
static func f() -> Int { S.i }
func g() -> Any { S.self }
func h() -> S { S(j: 2) }
func i() -> KeyPath<S, Int> { \\S.j }
func j(@Wrap(-S.i, S.i) n: Int = S.i) {}
}
"""),
Example("""
struct S {
struct T {
static let i = 3
}
struct R {
static let j = S.T.i
}
static let h = S.T.i + S.R.j
}
"""),
Example("""
enum E {
case A
static func f() -> E { E.A }
static func g() -> E { E.f() }
}
"""),
Example("""
extension E {
class C {
static var i = 2
var j: Int { C.i }
var k: Int {
get { C.i }
set { C.i = newValue }
}
}
}
""", excludeFromDocumentation: true),
Example("""
class C {
typealias A = C
let d: C? = nil
var c: C { C() }
let b: [C] = [C]()
init() {}
func f(e: C) -> C {
let f: C = C()
return f
}
func g(a: [C]) -> [C] { a }
}
final class D {
typealias A = D
let c: D? = nil
var d: D { D() }
let b: [D] = [D]()
init() {}
func f(e: D) -> D {
let f: D = D()
return f
}
func g(a: [D]) -> [D] { a }
}
struct S {
typealias A = S
// let s: S? = nil // Struct cannot contain itself
var t: S { S() }
let b: [S] = [S]()
init() {}
func f(e: S) -> S {
let f: S = S()
return f
}
func g(a: [S]) -> [S] { a }
}
""", excludeFromDocumentation: true)
]
static let corrections = [
Example("""
final class CheckCellView: NSTableCellView {
@IBOutlet var checkButton: NSButton!
override func awakeFromNib() {
checkButton.action = #selector(CheckCellView.check(_:))
}
@objc func check(_ button: AnyObject?) {}
}
"""):
Example("""
final class CheckCellView: NSTableCellView {
@IBOutlet var checkButton: NSButton!
override func awakeFromNib() {
checkButton.action = #selector(Self.check(_:))
}
@objc func check(_ button: AnyObject?) {}
}
"""),
Example("""
struct S {
static let i = 1
static let j = S.i
let k = S . j
static func f(_ l: Int = S.i) -> Int { l*S.j }
func g() { S.i + S.f() + k }
}
"""): Example("""
struct S {
static let i = 1
static let j = Self.i
let k = Self . j
static func f(_ l: Int = Self.i) -> Int { l*Self.j }
func g() { Self.i + Self.f() + k }
}
""")
]
}

View File

@ -1,185 +0,0 @@
import SwiftSyntax
struct RedundantSelfInClosureRule: SwiftSyntaxRule, CorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static var description = RuleDescription(
identifier: "redundant_self_in_closure",
name: "Redundant Self in Closure",
description: "Explicit use of 'self' is not required",
kind: .style,
nonTriggeringExamples: RedundantSelfInClosureRuleExamples.nonTriggeringExamples,
triggeringExamples: RedundantSelfInClosureRuleExamples.triggeringExamples,
corrections: RedundantSelfInClosureRuleExamples.corrections
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
ContextVisitor()
}
func correct(file: SwiftLintFile) -> [Correction] {
let ranges = ContextVisitor()
.walk(file: file, handler: \.corrections)
.compactMap { file.stringView.NSRange(start: $0.start, end: $0.end) }
.filter { file.ruleEnabled(violatingRange: $0, for: self) != nil }
.reversed()
var corrections = [Correction]()
var contents = file.contents
for range in ranges {
let contentsNSString = contents.bridge()
contents = contentsNSString.replacingCharacters(in: range, with: "")
let location = Location(file: file, characterOffset: range.location)
corrections.append(Correction(ruleDescription: Self.description, location: location))
}
file.write(contents)
return corrections
}
}
private enum TypeDeclarationKind {
case likeStruct
case likeClass
}
private enum FunctionCallType {
case anonymousClosure
case function
}
private enum SelfCaptureKind {
case strong
case weak
case uncaptured
}
private class ContextVisitor: DeclaredIdentifiersTrackingVisitor {
private var typeDeclarations = Stack<TypeDeclarationKind>()
private var functionCalls = Stack<FunctionCallType>()
private var selfCaptures = Stack<SelfCaptureKind>()
private(set) var corrections = [(start: AbsolutePosition, end: AbsolutePosition)]()
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .extensionsAndProtocols }
override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeClass)
return .visitChildren
}
override func visitPost(_ node: ActorDeclSyntax) {
typeDeclarations.pop()
}
override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeClass)
return .visitChildren
}
override func visitPost(_ node: ClassDeclSyntax) {
typeDeclarations.pop()
}
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
if let selfItem = node.signature?.capture?.items?.first(where: \.capturesSelf) {
selfCaptures.push(selfItem.capturesWeakly ? .weak : .strong)
} else {
selfCaptures.push(.uncaptured)
}
return .visitChildren
}
override func visitPost(_ node: ClosureExprSyntax) {
guard let activeTypeDeclarationKind = typeDeclarations.peek(),
let activeFunctionCallType = functionCalls.peek(),
let activeSelfCaptureKind = selfCaptures.peek() else {
return
}
let localCorrections = ExplicitSelfVisitor(
typeDeclarationKind: activeTypeDeclarationKind,
functionCallType: activeFunctionCallType,
selfCaptureKind: activeSelfCaptureKind,
scope: scope
).walk(tree: node.statements, handler: \.corrections)
violations.append(contentsOf: localCorrections.map(\.start))
corrections.append(contentsOf: localCorrections)
selfCaptures.pop()
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeStruct)
return .visitChildren
}
override func visitPost(_ node: EnumDeclSyntax) {
typeDeclarations.pop()
}
override func visit(_ node: FunctionCallExprSyntax) -> SyntaxVisitorContinueKind {
if node.calledExpression.is(ClosureExprSyntax.self) {
functionCalls.push(.anonymousClosure)
} else {
functionCalls.push(.function)
}
return .visitChildren
}
override func visitPost(_ node: FunctionCallExprSyntax) {
functionCalls.pop()
}
override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
typeDeclarations.push(.likeStruct)
return .visitChildren
}
override func visitPost(_ node: StructDeclSyntax) {
typeDeclarations.pop()
}
}
private class ExplicitSelfVisitor: DeclaredIdentifiersTrackingVisitor {
private let typeDeclKind: TypeDeclarationKind
private let functionCallType: FunctionCallType
private let selfCaptureKind: SelfCaptureKind
private(set) var corrections = [(start: AbsolutePosition, end: AbsolutePosition)]()
init(typeDeclarationKind: TypeDeclarationKind,
functionCallType: FunctionCallType,
selfCaptureKind: SelfCaptureKind,
scope: Scope) {
self.typeDeclKind = typeDeclarationKind
self.functionCallType = functionCallType
self.selfCaptureKind = selfCaptureKind
super.init(scope: scope)
}
override func visitPost(_ node: MemberAccessExprSyntax) {
if !hasSeenDeclaration(for: node.name.text), node.isBaseSelf, isSelfRedundant {
corrections.append(
(start: node.positionAfterSkippingLeadingTrivia, end: node.dot.endPositionBeforeTrailingTrivia)
)
}
}
override func visit(_ node: ClosureExprSyntax) -> SyntaxVisitorContinueKind {
// Will be handled separately by the parent visitor.
.skipChildren
}
var isSelfRedundant: Bool {
typeDeclKind == .likeStruct
|| functionCallType == .anonymousClosure
|| selfCaptureKind == .strong && SwiftVersion.current >= .fiveDotThree
|| selfCaptureKind == .weak && SwiftVersion.current >= .fiveDotEight
}
}
private extension MemberAccessExprSyntax {
var isBaseSelf: Bool {
base?.as(IdentifierExprSyntax.self)?.isSelf == true
}
}

View File

@ -1,227 +0,0 @@
struct RedundantSelfInClosureRuleExamples {
static let nonTriggeringExamples = [
Example("""
struct S {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f {
x = 1
f { x = 1 }
g()
}
}
}
"""),
Example("""
class C {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f { [weak self] in
self?.x = 1
self?.g()
guard let self = self ?? C() else { return }
self?.x = 1
}
C().f { self.x = 1 }
f { [weak self] in if let self { x = 1 } }
}
}
"""),
Example("""
struct S {
var x = 0, error = 0, exception = 0
var y: Int?, z: Int?, u: Int, v: Int?, w: Int?
func f(_ work: @escaping (Int) -> Void) { work() }
func g(x: Int) {
f { u in
self.x = x
let x = 1
self.x = 2
if let y, let v {
self.y = 3
self.v = 1
}
guard let z else {
let v = 4
self.x = 5
self.v = 6
return
}
self.z = 7
while let v { self.v = 8 }
for w in [Int]() { self.w = 9 }
self.u = u
do {} catch { self.error = 10 }
do {} catch let exception { self.exception = 11 }
}
}
}
"""),
Example("""
enum E {
case a(Int)
case b(Int, Int)
}
struct S {
var x: E = .a(3), y: Int, z: Int
func f(_ work: @escaping () -> Void) { work() }
func g(x: Int) {
f {
switch x {
case let .a(y):
self.y = 1
case .b(let y, var z):
self.y = 2
self.z = 3
}
}
}
}
""")
]
static let triggeringExamples = [
Example("""
struct S {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f {
self.x = 1
if self.x == 1 { self.g() }
}
}
}
"""),
Example("""
class C {
var x = 0
func g() {
{
self.x = 1
self.g()
}()
}
}
"""),
Example("""
class C {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f { [self] in
self.x = 1
self.g()
f { self.x = 1 }
}
}
}
"""),
Example("""
class C {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f { [unowned self] in self.x = 1 }
f { [self = self] in self.x = 1 }
f { [s = self] in s.x = 1 }
}
}
"""),
Example("""
struct S {
var x = 0
var y: Int?, z: Int?, v: Int?, w: Int?
func f(_ work: @escaping () -> Void) { work() }
func g(w: Int, _ v: Int) {
f {
self.w = 1
self.x = 2
if let y { self.x = 3 }
else { self.y = 3 }
guard let z else {
self.z = 4
self.x = 5
return
}
self.y = 6
while let y { self.x = 7 }
for y in [Int]() { self.x = 8 }
self.v = 9
do {
let x = 10
self.x = 11
}
self.x = 12
}
}
}
"""),
Example("""
struct S {
func f(_ work: @escaping () -> Void) { work() }
func g() {
f { let g = self.g() }
}
}
""", excludeFromDocumentation: true)
] + triggeringCompilerSpecificExamples
static let corrections = [
Example("""
struct S {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f {
self.x = 1
if self.x == 1 { self.g() }
}
}
}
"""): Example("""
struct S {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f {
x = 1
if x == 1 { g() }
}
}
}
""")
]
#if compiler(>=5.8)
private static let triggeringCompilerSpecificExamples = [
Example("""
class C {
var x = 0
func f(_ work: @escaping () -> Void) { work() }
func g() {
f { [weak self] in
self?.x = 1
guard let self else { return }
self.x = 1
}
f { [weak self] in
self?.x = 1
if let self = self else { self.x = 1 }
self?.x = 1
}
f { [weak self] in
self?.x = 1
while let self else { self.x = 1 }
self?.x = 1
}
}
}
""")
]
#else
private static let triggeringCompilerSpecificExamples = [Example]()
#endif
}

View File

@ -1,156 +0,0 @@
import SwiftSyntax
// MARK: - SelfBindingRule
struct SelfBindingRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule, OptInRule {
var configuration = SelfBindingConfiguration()
static let description = RuleDescription(
identifier: "self_binding",
name: "Self Binding",
description: "Re-bind `self` to a consistent identifier name.",
kind: .style,
nonTriggeringExamples: [
Example("if let self = self { return }"),
Example("guard let self = self else { return }"),
Example("if let this = this { return }"),
Example("guard let this = this else { return }"),
Example("if let this = self { return }", configuration: ["bind_identifier": "this"]),
Example("guard let this = self else { return }", configuration: ["bind_identifier": "this"])
],
triggeringExamples: [
Example("if let ↓`self` = self { return }"),
Example("guard let ↓`self` = self else { return }"),
Example("if let ↓this = self { return }"),
Example("guard let ↓this = self else { return }"),
Example("if let ↓self = self { return }", configuration: ["bind_identifier": "this"]),
Example("guard let ↓self = self else { return }", configuration: ["bind_identifier": "this"]),
Example("if let ↓self { return }", configuration: ["bind_identifier": "this"]),
Example("guard let ↓self else { return }", configuration: ["bind_identifier": "this"])
],
corrections: [
Example("if let ↓`self` = self { return }"):
Example("if let self = self { return }"),
Example("guard let ↓`self` = self else { return }"):
Example("guard let self = self else { return }"),
Example("if let ↓this = self { return }"):
Example("if let self = self { return }"),
Example("guard let ↓this = self else { return }"):
Example("guard let self = self else { return }"),
Example("if let ↓self = self { return }", configuration: ["bind_identifier": "this"]):
Example("if let this = self { return }", configuration: ["bind_identifier": "this"]),
Example("if let ↓self { return }", configuration: ["bind_identifier": "this"]):
Example("if let this = self { return }", configuration: ["bind_identifier": "this"]),
Example("guard let ↓self else { return }", configuration: ["bind_identifier": "this"]):
Example("guard let this = self else { return }", configuration: ["bind_identifier": "this"])
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
SelfBindingRuleVisitor(bindIdentifier: configuration.bindIdentifier)
}
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
SelfBindingRuleRewriter(
bindIdentifier: configuration.bindIdentifier,
locationConverter: file.locationConverter,
disabledRegions: disabledRegions(file: file)
)
}
}
// MARK: - SelfBindingRuleVisitor
private final class SelfBindingRuleVisitor: ViolationsSyntaxVisitor {
private let bindIdentifier: String
init(bindIdentifier: String) {
self.bindIdentifier = bindIdentifier
super.init(viewMode: .sourceAccurate)
}
override func visitPost(_ node: OptionalBindingConditionSyntax) {
if let identifierPattern = node.pattern.as(IdentifierPatternSyntax.self),
identifierPattern.identifier.text != bindIdentifier {
var hasViolation = false
if let initializerIdentifier = node.initializer?.value.as(IdentifierExprSyntax.self) {
hasViolation = initializerIdentifier.identifier.text == "self"
} else if node.initializer == nil {
hasViolation = identifierPattern.identifier.text == "self" && bindIdentifier != "self"
}
if hasViolation {
violations.append(
ReasonedRuleViolation(
position: identifierPattern.positionAfterSkippingLeadingTrivia,
reason: "`self` should always be re-bound to `\(bindIdentifier)`"
)
)
}
}
}
}
// MARK: - SelfBindingRuleRewriter
private final class SelfBindingRuleRewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
private(set) var correctionPositions: [AbsolutePosition] = []
private let bindIdentifier: String
let locationConverter: SourceLocationConverter
let disabledRegions: [SourceRange]
init(bindIdentifier: String, locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
self.bindIdentifier = bindIdentifier
self.locationConverter = locationConverter
self.disabledRegions = disabledRegions
}
override func visit(_ node: OptionalBindingConditionSyntax) -> OptionalBindingConditionSyntax {
guard
let identifierPattern = node.pattern.as(IdentifierPatternSyntax.self),
identifierPattern.identifier.text != bindIdentifier,
!node.isContainedIn(regions: disabledRegions, locationConverter: locationConverter)
else {
return super.visit(node)
}
if let initializerIdentifier = node.initializer?.value.as(IdentifierExprSyntax.self),
initializerIdentifier.identifier.text == "self" {
correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia)
let newPattern = PatternSyntax(
identifierPattern
.with(\.identifier,
identifierPattern.identifier.with(\.tokenKind, .identifier(bindIdentifier))
)
)
return super.visit(node.with(\.pattern, newPattern))
} else if node.initializer == nil, identifierPattern.identifier.text == "self", bindIdentifier != "self" {
correctionPositions.append(identifierPattern.positionAfterSkippingLeadingTrivia)
let newPattern = PatternSyntax(
identifierPattern
.with(\.identifier,
identifierPattern.identifier.with(\.tokenKind, .identifier(bindIdentifier)))
)
let newInitializer = InitializerClauseSyntax(
value: IdentifierExprSyntax(
identifier: .keyword(
.`self`,
leadingTrivia: .space,
trailingTrivia: identifierPattern.trailingTrivia
)
)
)
let newNode = node
.with(\.pattern, newPattern)
.with(\.initializer, newInitializer)
return super.visit(newNode)
} else {
return super.visit(node)
}
}
}

View File

@ -1,118 +0,0 @@
import SwiftSyntax
struct SortedEnumCasesRule: ConfigurationProviderRule, SwiftSyntaxRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static let description = RuleDescription(
identifier: "sorted_enum_cases",
name: "Sorted Enum Cases",
description: "Enum cases should be sorted",
kind: .style,
nonTriggeringExamples: [
Example("""
enum foo {
case a
case b
case c
}
"""),
Example("""
enum foo {
case a, b, c
}
"""),
Example("""
enum foo {
case a
case b, c
}
"""),
Example("""
enum foo {
case a(foo: Foo)
case b(String), c
}
"""),
Example("""
@frozen
enum foo {
case b
case a
case c, f, d
}
""")
],
triggeringExamples: [
Example("""
enum foo {
case b
case a
case c
}
"""),
Example("""
enum foo {
case b, a, c
}
"""),
Example("""
enum foo {
case b, c
case a
}
"""),
Example("""
enum foo {
case a
case b, d, c
}
"""),
Example("""
enum foo {
case a(foo: Foo)
case c, b(String)
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private extension SortedEnumCasesRule {
final class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] {
return .allExcept(EnumDeclSyntax.self)
}
override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
guard !node.attributes.contains(attributeNamed: "frozen") else {
return .skipChildren
}
let cases = node.memberBlock.members.compactMap { $0.decl.as(EnumCaseDeclSyntax.self) }
let sortedCases = cases
.sorted(by: { $0.elements.first!.identifier.text < $1.elements.first!.identifier.text })
zip(sortedCases, cases).forEach { sortedCase, currentCase in
if sortedCase.elements.first?.identifier.text != currentCase.elements.first?.identifier.text {
violations.append(currentCase.positionAfterSkippingLeadingTrivia)
}
}
return .visitChildren
}
override func visitPost(_ node: EnumCaseDeclSyntax) {
let sortedElements = node.elements.sorted(by: { $0.identifier.text < $1.identifier.text })
zip(sortedElements, node.elements).forEach { sortedElement, currentElement in
if sortedElement.identifier.text != currentElement.identifier.text {
violations.append(currentElement.positionAfterSkippingLeadingTrivia)
}
}
}
}
}

View File

@ -1,233 +0,0 @@
internal struct SortedImportsRuleExamples {
private static let groupByAttributesConfiguration = ["grouping": "attributes"]
static let nonTriggeringExamples = [
Example("""
import AAA
import BBB
import CCC
import DDD
"""),
Example("""
import Alamofire
import API
"""),
Example("""
import labc
import Ldef
"""),
Example("""
import BBB
// comment
import AAA
import CCC
"""),
Example("""
@testable import AAA
import CCC
"""),
Example("""
import AAA
@testable import CCC
"""),
Example("""
import EEE.A
import FFF.B
#if os(Linux)
import DDD.A
import EEE.B
#else
import CCC
import DDD.B
#endif
import AAA
import BBB
"""),
Example("""
@testable import AAA
@testable import BBB
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true),
Example("""
@testable import BBB
import AAA
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true),
Example("""
@_exported import BBB
@testable import AAA
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true),
Example("""
@_exported @testable import BBB
import AAA
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true)
]
static let triggeringExamples = [
Example("""
import AAA
import ZZZ
import BBB
import CCC
"""),
Example("""
import DDD
// comment
import CCC
import AAA
"""),
Example("""
@testable import CCC
import AAA
"""),
Example("""
import CCC
@testable import AAA
"""),
Example("""
import FFF.B
import EEE.A
#if os(Linux)
import DDD.A
import EEE.B
#else
import DDD.B
import CCC
#endif
import AAA
import BBB
"""),
Example("""
@testable import BBB
@testable import AAA
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true),
Example("""
import AAA
@testable import BBB
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true),
Example("""
import BBB
@testable import AAA
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true),
Example("""
@testable import AAA
@_exported import BBB
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true),
Example("""
import AAA
@_exported @testable import BBB
""", configuration: groupByAttributesConfiguration, excludeFromDocumentation: true)
]
static let corrections = [
Example("""
import AAA
import ZZZ
import BBB
import CCC
"""):
Example("""
import AAA
import BBB
import CCC
import ZZZ
"""),
Example("""
import BBB // comment
import AAA
"""): Example("""
import AAA
import BBB // comment
"""),
Example("""
import BBB
// comment
import CCC
import AAA
"""):
Example("""
import BBB
// comment
import AAA
import CCC
"""),
Example("""
@testable import CCC
import AAA
"""): Example("""
import AAA
@testable import CCC
"""),
Example("""
import CCC
@testable import AAA
"""): Example("""
@testable import AAA
import CCC
"""),
Example("""
import FFF.B
import EEE.A
#if os(Linux)
import DDD.A
import EEE.B
#else
import DDD.B
import CCC
#endif
import AAA
import BBB
"""):
Example("""
import EEE.A
import FFF.B
#if os(Linux)
import DDD.A
import EEE.B
#else
import CCC
import DDD.B
#endif
import AAA
import BBB
"""),
Example("""
@testable import BBB
@testable import AAA
""", configuration: groupByAttributesConfiguration):
Example("""
@testable import AAA
@testable import BBB
"""),
Example("""
import AAA
@testable import BBB
""", configuration: groupByAttributesConfiguration):
Example("""
@testable import BBB
import AAA
"""),
Example("""
import BBB
@testable import AAA
""", configuration: groupByAttributesConfiguration):
Example("""
@testable import AAA
import BBB
"""),
Example("""
@testable import AAA
@_exported import BBB
""", configuration: groupByAttributesConfiguration):
Example("""
@_exported import BBB
@testable import AAA
"""),
Example("""
import AAA
@_exported @testable import BBB
""", configuration: groupByAttributesConfiguration):
Example("""
@_exported @testable import BBB
import AAA
""")
]
}

View File

@ -1,150 +0,0 @@
import SwiftSyntax
struct SuperfluousElseRule: SwiftSyntaxRule, ConfigurationProviderRule, OptInRule {
var configuration = SeverityConfiguration<Self>(.warning)
static var description = RuleDescription(
identifier: "superfluous_else",
name: "Superfluous Else",
description: "Else branches should be avoided when the previous if-block exits the current scope",
kind: .style,
nonTriggeringExamples: [
Example("""
if i > 0 {
// comment
} else if i < 12 {
return 2
} else {
return 3
}
"""),
Example("""
if i > 0 {
let a = 1
if a > 1 {
// comment
} else {
return 1
}
// comment
} else {
return 3
}
"""),
Example("""
if i > 0 {
if a > 1 {
return 1
}
} else {
return 3
}
"""),
Example("""
if i > 0 {
if a > 1 {
if a > 1 {
// comment
} else {
return 1
}
}
} else {
return 3
}
""", excludeFromDocumentation: true)
],
triggeringExamples: [
Example("""
if i > 0 {
return 1
// comment
} else {
return 2
}
"""),
Example("""
if i > 0 {
return 1
} else if i < 12 {
return 2
} else if i > 18 {
return 3
}
"""),
Example("""
if i > 0 {
if i < 12 {
return 5
} else {
if i > 11 {
return 6
} else {
return 7
}
}
} else if i < 12 {
return 2
} else if i < 24 {
return 8
} else {
return 3
}
""")
]
)
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
Visitor(viewMode: .sourceAccurate)
}
}
private class Visitor: ViolationsSyntaxVisitor {
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
override func visitPost(_ node: IfExprSyntax) {
if node.violatesRule {
violations.append(node.ifKeyword.positionAfterSkippingLeadingTrivia)
}
}
}
private extension IfExprSyntax {
var violatesRule: Bool {
if elseKeyword == nil {
return false
}
let thenBodyReturns = lastStatementReturns(in: body)
if thenBodyReturns, let parent = parent?.as(IfExprSyntax.self) {
return parent.violatesRule
}
return thenBodyReturns
}
private var returnsInAllBranches: Bool {
guard lastStatementReturns(in: body) else {
return false
}
if case let .ifExpr(nestedIfStmt) = elseBody {
return nestedIfStmt.returnsInAllBranches
}
if case let .codeBlock(block) = elseBody {
return lastStatementReturns(in: block)
}
return false
}
private func lastStatementReturns(in block: CodeBlockSyntax) -> Bool {
guard let lastItem = block.statements.last?.as(CodeBlockItemSyntax.self)?.item else {
return false
}
if lastItem.is(ReturnStmtSyntax.self) {
return true
}
if let exprStmt = lastItem.as(ExpressionStmtSyntax.self),
let lastIfStmt = exprStmt.expression.as(IfExprSyntax.self) {
return lastIfStmt.returnsInAllBranches
}
return false
}
}

View File

@ -1,123 +0,0 @@
import Dispatch
public extension Array where Element: Equatable {
/// The elements in this array, discarding duplicates after the first one.
/// Order-preserving.
var unique: [Element] {
var uniqueValues = [Element]()
for item in self where !uniqueValues.contains(item) {
uniqueValues.append(item)
}
return uniqueValues
}
}
public extension Array where Element: Hashable {
/// Produces an array containing the passed `obj` value.
/// If `obj` is an array already, return it.
/// If `obj` is a set, copy its elements to a new array.
/// If `obj` is a value of type `Element`, return a single-item array containing it.
///
/// - parameter obj: The input.
///
/// - returns: The produced array.
static func array(of obj: Any?) -> [Element]? {
if let array = obj as? [Element] {
return array
} else if let set = obj as? Set<Element> {
return Array(set)
} else if let obj = obj as? Element {
return [obj]
}
return nil
}
}
public extension Array {
/// Produces an array containing the passed `obj` value.
/// If `obj` is an array already, return it.
/// If `obj` is a value of type `Element`, return a single-item array containing it.
///
/// - parameter obj: The input.
///
/// - returns: The produced array.
static func array(of obj: Any?) -> [Element]? {
if let array = obj as? [Element] {
return array
} else if let obj = obj as? Element {
return [obj]
}
return nil
}
/// Group the elements in this array into a dictionary, keyed by applying the specified `transform`.
///
/// - parameter transform: The transformation function to extract an element to its group key.
///
/// - returns: The elements grouped by applying the specified transformation.
func group<U: Hashable>(by transform: (Element) -> U) -> [U: [Element]] {
return Dictionary(grouping: self, by: { transform($0) })
}
/// Returns the elements failing the `belongsInSecondPartition` test, followed by the elements passing the
/// `belongsInSecondPartition` test.
///
/// - parameter belongsInSecondPartition: The test function to determine if the element should be in the second
/// partition.
///
/// - returns: The elements failing the `belongsInSecondPartition` test, followed by the elements passing the
/// `belongsInSecondPartition` test.
func partitioned(by belongsInSecondPartition: (Element) throws -> Bool) rethrows ->
(first: ArraySlice<Element>, second: ArraySlice<Element>) {
var copy = self
let pivot = try copy.partition(by: belongsInSecondPartition)
return (copy[0..<pivot], copy[pivot..<count])
}
/// Same as `flatMap` but spreads the work in the `transform` block in parallel using GCD's `concurrentPerform`.
///
/// - parameter transform: The transformation to apply to each element.
///
/// - returns: The result of applying `transform` on every element and flattening the results.
func parallelFlatMap<T>(transform: (Element) -> [T]) -> [T] {
return parallelMap(transform: transform).flatMap { $0 }
}
/// Same as `compactMap` but spreads the work in the `transform` block in parallel using GCD's `concurrentPerform`.
///
/// - parameter transform: The transformation to apply to each element.
///
/// - returns: The result of applying `transform` on every element and discarding the `nil` ones.
func parallelCompactMap<T>(transform: (Element) -> T?) -> [T] {
return parallelMap(transform: transform).compactMap { $0 }
}
/// Same as `map` but spreads the work in the `transform` block in parallel using GCD's `concurrentPerform`.
///
/// - parameter transform: The transformation to apply to each element.
///
/// - returns: The result of applying `transform` on every element.
func parallelMap<T>(transform: (Element) -> T) -> [T] {
var result = ContiguousArray<T?>(repeating: nil, count: count)
return result.withUnsafeMutableBufferPointer { buffer in
DispatchQueue.concurrentPerform(iterations: buffer.count) { idx in
buffer[idx] = transform(self[idx])
}
return buffer.map { $0! }
}
}
}
public extension Collection {
/// Whether this collection has one or more element.
var isNotEmpty: Bool {
return !isEmpty
}
/// Get the only element in the collection.
///
/// If the collection is empty or contains more than one element the result will be `nil`.
var onlyElement: Element? {
count == 1 ? first : nil
}
}

View File

@ -1,15 +0,0 @@
import SwiftIDEUtils
public extension SyntaxClassification {
// True if it is any kind of comment.
var isComment: Bool {
switch self {
case .lineComment, .docLineComment, .blockComment, .docBlockComment:
return true
case .none, .keyword, .identifier, .typeIdentifier, .operatorIdentifier, .dollarIdentifier, .integerLiteral,
.floatingLiteral, .stringLiteral, .stringInterpolationAnchor, .poundDirectiveKeyword, .buildConfigId,
.attribute, .objectLiteral, .editorPlaceholder, .regexLiteral:
return false
}
}
}

View File

@ -1,36 +0,0 @@
#if os(macOS)
import Foundation
import MachO
#endif
/// Information about this executable.
public enum ExecutableInfo {
/// A stable identifier for this executable. Uses the Mach-O header UUID on macOS. Nil on Linux.
public static let buildID: String? = {
#if os(macOS)
func getUUID(pointer: UnsafeRawPointer) -> UUID? {
var offset: UInt64 = 0
let header = pointer.bindMemory(to: mach_header_64.self, capacity: 1)
offset += UInt64(MemoryLayout<mach_header_64>.size)
for _ in 0..<header.pointee.ncmds {
let loadCommand = pointer.load(fromByteOffset: Int(offset), as: load_command.self)
if loadCommand.cmd == LC_UUID {
let uuidCommand = pointer.load(fromByteOffset: Int(offset), as: uuid_command.self)
return UUID(uuid: uuidCommand.uuid)
}
offset += UInt64(loadCommand.cmdsize)
}
return nil
}
if let handle = dlopen(nil, RTLD_LAZY) {
defer { dlclose(handle) }
if let ptr = dlsym(handle, MH_EXECUTE_SYM) {
return getUUID(pointer: ptr)?.uuidString
}
}
#endif
return nil
}()
}

View File

@ -1,62 +0,0 @@
/// A basic stack type implementing the LIFO principle - only the last inserted element can be accessed and removed.
public struct Stack<Element> {
private var elements = [Element]()
/// Creates an empty `Stack`.
public init() {}
/// The number of elements in this stack.
public var count: Int {
elements.count
}
/// Pushes (appends) an element onto the stack.
///
/// - parameter element: The element to push onto the stack.
public mutating func push(_ element: Element) {
elements.append(element)
}
/// Removes and returns the last element of the stack.
///
/// - returns: The last element of the stack if the stack is not empty; otherwise, nil.
@discardableResult
public mutating func pop() -> Element? {
elements.popLast()
}
/// Returns the last element of the stack if the stack is not empty; otherwise, nil.
public func peek() -> Element? {
elements.last
}
/// Check whether the sequence contains an element that satisfies the given predicate.
///
/// - parameter predicate: A closure that takes an element of the sequence
/// and returns whether it represents a match.
/// - returns: `true` if the sequence contains an element that satisfies `predicate`.
public func contains(where predicate: (Element) -> Bool) -> Bool {
elements.contains(where: predicate)
}
/// Modify the last element.
///
/// - parameter modifier: A function to applied to the last element to modify it in place.
public mutating func modifyLast(by modifier: (inout Element) -> Void) {
if elements.isNotEmpty {
modifier(&elements[count - 1])
}
}
}
extension Stack: CustomDebugStringConvertible where Element == CustomDebugStringConvertible {
public var debugDescription: String {
let intermediateElements = count > 1 ? elements[1 ..< count - 1] : []
return """
Stack with \(count) elements:
first: \(elements.first?.debugDescription ?? "")
intermediate: \(intermediateElements.map(\.debugDescription).joined(separator: ", "))
last: \(peek()?.debugDescription ?? "")
"""
}
}

View File

@ -1,34 +0,0 @@
/// A rule configuration that allows to disable (`off`) an option of a rule or specify its severity level in which
/// case it's active.
public struct ChildOptionSeverityConfiguration<Parent: Rule>: RuleConfiguration, Equatable {
/// Configuration with a warning severity.
public static var error: Self { Self(optionSeverity: .error) }
/// Configuration with an error severity.
public static var warning: Self { Self(optionSeverity: .warning) }
/// Configuration disabling an option.
public static var off: Self { Self(optionSeverity: .off) }
enum ChildOptionSeverity: String {
case warning, error, off
}
private var optionSeverity: ChildOptionSeverity
public var consoleDescription: String {
optionSeverity.rawValue
}
/// The `ChildOptionSeverityConfiguration` mapped to a usually used `ViolationSeverity`. It's `nil` if the option
/// is set to `off`.
public var severity: ViolationSeverity? {
ViolationSeverity(rawValue: optionSeverity.rawValue)
}
public mutating func apply(configuration: Any) throws {
guard let configString = configuration as? String,
let optionSeverity = ChildOptionSeverity(rawValue: configString.lowercased()) else {
throw Issue.unknownConfiguration(ruleID: Parent.description.identifier)
}
self.optionSeverity = optionSeverity
}
}

View File

@ -1,2 +0,0 @@
@_spi(TestHelper)
public typealias ConfigurationRuleWrapper = (rule: Rule, initializedWithNonEmptyConfiguration: Bool)

View File

@ -1,33 +0,0 @@
import Foundation
/// Utility to measure the time spent in each custom rule.
public final class CustomRuleTimer {
private let lock = NSLock()
private var ruleIDForTimes = [String: [TimeInterval]]()
private var shouldRecord = false
/// Singleton.
public static let shared = CustomRuleTimer()
/// Tell the timer it should record time spent in rules.
public func activate() {
shouldRecord = true
}
/// Return all time spent for each custom rule, keyed by rule ID.
public func dump() -> [String: TimeInterval] {
ruleIDForTimes.mapValues { $0.reduce(0, +) }
}
/// Register time spent evaluating a rule with the specified ID.
///
/// - parameter time: The time interval spent evaluating this rule ID.
/// - parameter ruleID: The ID of the rule that was evaluated.
func register(time: TimeInterval, forRuleID ruleID: String) {
guard shouldRecord else { return }
lock.lock()
defer { lock.unlock() }
ruleIDForTimes[ruleID, default: []].append(time)
}
}

View File

@ -1,119 +0,0 @@
import Foundation
/// All possible SwiftLint issues which are printed as warnings by default.
public enum Issue: LocalizedError, Equatable {
/// The configuration didn't match internal expectations.
case unknownConfiguration(ruleID: String)
/// Rule is listed multiple times in the configuration.
case listedMultipleTime(ruleID: String, times: Int)
/// An identifier `old` has been renamed to `new`.
case renamedIdentifier(old: String, new: String)
/// Configuration for a rule is invalid.
case invalidConfiguration(ruleID: String)
/// Some configuration keys are invalid.
case invalidConfigurationKeys([String])
/// A generic warning specified by a string.
case genericWarning(String)
/// A generic error specified by a string.
case genericError(String)
/// A deprecation warning for a rule.
case ruleDeprecated(ruleID: String)
/// The initial configuration file was not found.
case initialFileNotFound(path: String)
/// The file at `path` is not readable or cannot be opened.
case fileNotReadable(path: String?, ruleID: String)
/// The file at `path` is not writable.
case fileNotWritable(path: String)
/// The file at `path` cannot be indexed by a specific rule.
case indexingError(path: String?, ruleID: String)
/// No arguments were provided to compile a file at `path` within a specific rule.
case missingCompilerArguments(path: String?, ruleID: String)
/// Cursor information cannot be extracted from a specific location.
case missingCursorInfo(path: String?, ruleID: String)
/// An error that occurred when parsing YAML.
case yamlParsing(String)
/// Wraps any `Error` into a `SwiftLintError.genericWarning` if it is not already a `SwiftLintError`.
///
/// - parameter error: Any `Error`.
///
/// - returns: A `SwiftLintError.genericWarning` containig the message of the `error` argument.
static func wrap(error: Error) -> Self {
error as? Issue ?? Self.genericWarning(error.localizedDescription)
}
/// Make this issue an error.
var asError: Self {
Self.genericError(message)
}
/// The issues description which is ready to be printed to the console.
var errorDescription: String {
switch self {
case .genericError:
return "error: \(message)"
case .genericWarning:
return "warning: \(message)"
default:
return Self.genericWarning(message).errorDescription
}
}
/// Print the issue to the console.
public func print() {
queuedPrintError(errorDescription)
}
private var message: String {
switch self {
case let .unknownConfiguration(id):
return "Invalid configuration for '\(id)' rule. Falling back to default."
case let .listedMultipleTime(id, times):
return "'\(id)' is listed \(times) times in the configuration."
case let .renamedIdentifier(old, new):
return "'\(old)' has been renamed to '\(new)' and will be completely removed in a future release."
case let .invalidConfiguration(id):
return "Invalid configuration for '\(id)'. Falling back to default."
case let .invalidConfigurationKeys(keys):
return "Configuration contains invalid keys \(keys.joined(separator: ", "))."
case let .genericWarning(message), let .genericError(message):
return message
case let .ruleDeprecated(id):
return """
The `\(id)` rule is now deprecated and will be \
completely removed in a future release.
"""
case let .initialFileNotFound(path):
return "Could not read file at path '\(path)'."
case let .fileNotReadable(path, id):
return "Cannot open or read file at path '\(path ?? "...")' within '\(id)' rule."
case let .fileNotWritable(path):
return "Cannot write to file at path '\(path)'."
case let .indexingError(path, id):
return "Cannot index file at path '\(path ?? "...")' within '\(id)' rule."
case let .missingCompilerArguments(path, id):
return """
Attempted to lint file at path '\(path ?? "...")' within '\(id)' rule \
without any compiler arguments.
"""
case let .missingCursorInfo(path, id):
return "Cannot get cursor info from file at path '\(path ?? "...")' within '\(id)' rule."
case let .yamlParsing(message):
return "Cannot parse YAML file: \(message)"
}
}
}

View File

@ -1,20 +0,0 @@
// Generated using Sourcery 2.0.2 https://github.com/krzysztofzablocki/Sourcery
// DO NOT EDIT
/// The reporters list containing all the reporters built into SwiftLint.
public let reportersList: [Reporter.Type] = [
CSVReporter.self,
CheckstyleReporter.self,
CodeClimateReporter.self,
EmojiReporter.self,
GitHubActionsLoggingReporter.self,
GitLabJUnitReporter.self,
HTMLReporter.self,
JSONReporter.self,
JUnitReporter.self,
MarkdownReporter.self,
RelativePathReporter.self,
SonarQubeReporter.self,
SummaryReporter.self,
XcodeReporter.self
]

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