Compare commits

..

1 Commits

Author SHA1 Message Date
JP Simard 527e2c4f16
release 0.51.0-rc.2 2023-02-20 18:54:11 -05:00
614 changed files with 4555 additions and 7595 deletions

View File

@ -5,9 +5,6 @@ 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 \

View File

@ -1 +1 @@
6.2.0
6.0.0

View File

@ -11,19 +11,11 @@ steps:
- 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: "TSan Tests"
commands:
- echo "+++ Test"
- bazel test --test_output=errors --build_tests_only --features=tsan --test_timeout=1000 //Tests/...
- label: "Sourcery"
commands:
- echo "+++ Run Sourcery"
- make --always-make sourcery
- echo "+++ Diff Files"
- git diff --quiet HEAD
- bazel test --test_output=streamed --build_tests_only --features=tsan --test_timeout=1000 //Tests/...

View File

@ -9,7 +9,7 @@ on:
jobs:
build:
runs-on: ubuntu-20.04
runs-on: ubuntu-latest
steps:
- uses: actions/checkout@v3

View File

@ -0,0 +1,30 @@
name: update_swift_syntax
on:
workflow_dispatch:
schedule:
# Mondays at 1pm UTC (9am EDT)
- cron: "0 13 * * 1"
jobs:
update_swift_syntax:
runs-on: macos-12
steps:
- name: Checkout source
uses: actions/checkout@v3
- name: Update SwiftSyntax
id: update-swift-syntax
run: ./tools/update-swift-syntax.sh
env:
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
- name: Create PR
if: steps.update-swift-syntax.outputs.needs_update == 'true'
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27
with:
title: Update SwiftSyntax
body: |
Diff: https://github.com/apple/swift-syntax/compare/${{ steps.update-swift-syntax.outputs.old_tag }}...${{ steps.update-swift-syntax.outputs.new_tag }}
commit-message: 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: '© 2022 [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:

View File

@ -1,14 +1,13 @@
@testable import SwiftLintBuiltInRules
@_spi(TestHelper)
@testable import SwiftLintCore
@testable 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,8 @@
/// The rule list containing all available rules built into SwiftLint.
public let builtInRules: [Rule.Type] = [
let builtInRules: [Rule.Type] = [
{% for rule in types.structs where rule.name|hasSuffix:"Rule" or rule.name|hasSuffix:"Rules" %} {{ rule.name }}.self{% if not forloop.last %},{% endif %}
{% endfor %}]
/// The rule list containing all available rules built into SwiftLint as well as native custom rules.
public let primaryRuleList = RuleList(rules: builtInRules + 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

@ -8,51 +8,76 @@ 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
- 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
- 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
- local_doc_comment
- 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
- period_spacing
- 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
- shorthand_optional_binding
- 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:
@ -65,27 +90,19 @@ 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 +122,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

76
BUILD
View File

@ -5,68 +5,29 @@ load(
"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(
@ -102,21 +63,6 @@ apple_universal_binary(
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 +81,6 @@ filegroup(
srcs = [
"BUILD",
"LICENSE",
"MODULE.bazel",
"//:DyldWarningWorkaroundSources",
"//:LintInputs",
"//Tests:BUILD",
"//bazel:release_files",

View File

@ -1,244 +1,4 @@
## Main
#### 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
## 0.51.0-rc.2: bzllint
#### Breaking
@ -251,13 +11,6 @@
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
@ -265,23 +18,13 @@
#### 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)
[#issue_number](https://github.com/realm/SwiftLint/issues/4609)
* Separate analyzer rules as an independent section in the rule directory of
the reference.
@ -292,6 +35,11 @@
[SimplyDanny](https://github.com/SimplyDanny)
[#4707](https://github.com/realm/SwiftLint/issues/4707)
* Interpret strings in `excluded` option of `identifier_name`,
`type_name` and `generic_type_name` rules as regex.
[Moly](https://github.com/kyounh12)
[#4655](https://github.com/realm/SwiftLint/pull/4655)
* 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)
@ -335,28 +83,6 @@
* 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
@ -368,11 +94,6 @@
[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)
@ -393,11 +114,6 @@
[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)
@ -423,16 +139,6 @@
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

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

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|\.bazelversion|WORKSPACE|bazel\/|BUILD/).empty?
# Add a CHANGELOG entry for app changes
if !modified_files.include?('CHANGELOG.md') && has_app_changes

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.7.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.13.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.17.0)
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.6)
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,15 +1,14 @@
module(
name = "swiftlint",
version = "0.52.2",
version = "0.51.0-rc.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 = "rules_apple", version = "2.0.0", repo_name = "build_bazel_rules_apple")
bazel_dep(name = "rules_swift", version = "1.5.1", repo_name = "build_bazel_rules_swift")
bazel_dep(name = "rules_xcodeproj", version = "1.1.0", repo_name = "com_github_buildbuddy_io_rules_xcodeproj")
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")
@ -17,7 +16,7 @@ bazel_dep(name = "yams", version = "5.0.5", repo_name = "sourcekitten_com_github
swiftlint_repos = use_extension("//bazel:repos.bzl", "swiftlint_repos_bzlmod")
use_repo(
swiftlint_repos,
"SwiftSyntax",
"com_github_apple_swift_syntax",
"com_github_johnsundell_collectionconcurrencykit",
"com_github_krzyzanowskim_cryptoswift",
"swiftlint_com_github_scottrhoyt_swifty_text_table",

View File

@ -30,18 +30,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
@ -69,7 +65,7 @@ clean:
clean_xcode:
$(BUILD_TOOL) $(XCODEFLAGS) -configuration Test clean
build:
build: clean
mkdir -p "$(SWIFTLINT_EXECUTABLE_PARENT)"
bazel build --config release universal_swiftlint
$(eval SWIFTLINT_BINARY := $(shell bazel cquery --config release --output=files universal_swiftlint))
@ -121,7 +117,6 @@ zip_linux_release:
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))
@ -152,8 +147,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
@ -170,9 +165,8 @@ 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 's/__VERSION__/$(NEW_VERSION)/g' tools/Version.swift.template > Source/SwiftLintFramework/Models/Version.swift
@sed -e '3s/.*/ version = "$(NEW_VERSION)",/' -i '' MODULE.bazel
make clean
make package
make bazel_release
make portable_zip
@ -182,11 +176,6 @@ endif
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

@ -9,15 +9,6 @@
"version" : "0.2.0"
}
},
{
"identity" : "cryptoswift",
"kind" : "remoteSourceControl",
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
"state" : {
"revision" : "32f641cf24fc7abc1c591a2025e9f2f572648b0f",
"version" : "1.7.2"
}
},
{
"identity" : "sourcekitten",
"kind" : "remoteSourceControl",
@ -41,8 +32,8 @@
"kind" : "remoteSourceControl",
"location" : "https://github.com/apple/swift-syntax.git",
"state" : {
"revision" : "165fc6d22394c1168ff76ab5d951245971ef07e5",
"version" : "509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-05-a"
"revision" : "013a48e2312e57b7b355db25bd3ea75282ebf274",
"version" : "0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a"
}
},
{

View File

@ -1,6 +1,25 @@
// swift-tools-version:5.7
import PackageDescription
#if os(macOS)
private let addCryptoSwift = false
private let binaryPlugin = true
#else
private let addCryptoSwift = true
private let binaryPlugin = false
#endif
let frameworkDependencies: [Target.Dependency] = [
.product(name: "IDEUtils", package: "swift-syntax"),
.product(name: "SourceKittenFramework", package: "SourceKitten"),
.product(name: "SwiftSyntax", package: "swift-syntax"),
.product(name: "SwiftSyntaxBuilder", package: "swift-syntax"),
.product(name: "SwiftParser", package: "swift-syntax"),
.product(name: "SwiftOperators", package: "swift-syntax"),
"Yams",
]
+ (addCryptoSwift ? ["CryptoSwift"] : [])
let package = Package(
name: "SwiftLint",
platforms: [.macOS(.v12)],
@ -11,20 +30,18 @@ let package = Package(
],
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/apple/swift-syntax.git", exact: "0.50900.0-swift-DEVELOPMENT-SNAPSHOT-2023-02-06-a"),
.package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.34.1")),
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.5"),
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.2.0"),
.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: binaryPlugin ? "SwiftLintBinary" : "swiftlint")
]
),
.executableTarget(
@ -42,38 +59,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: [
@ -114,8 +103,8 @@ let package = Package(
),
.binaryTarget(
name: "SwiftLintBinary",
url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "89651e1c87fb62faf076ef785a5b1af7f43570b2b74c6773526e0d5114e0578e"
url: "https://github.com/realm/SwiftLint/releases/download/0.51.0-rc.2/SwiftLintBinary-macos.artifactbundle.zip",
checksum: "1baf4850298292c0232cc0f6ca192ab2778b1bf26ee50a1f324857b6f6eeed58"
)
]
)

View File

@ -29,10 +29,6 @@ struct SwiftLintPlugin: BuildToolPlugin {
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)"
]

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
@ -158,10 +158,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 +200,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 +212,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 +227,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 +304,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
@ -401,7 +396,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 +474,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
@ -550,29 +543,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 +614,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

@ -8,7 +8,16 @@ For SwiftLint contributors, follow these steps to cut a release:
* Top Loading
* Fresh Out Of The Dryer
1. Make sure you have the latest stable Xcode version installed and
`xcode-select`ed
`xcode-select`ed.
1. Release new version: `make release "0.2.0: Tumble Dry"`
1. Wait for the Docker CI job to finish then run: `make zip_linux_release`
1. 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,
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,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,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,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,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,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 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,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,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,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,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,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,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,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,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,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
]

View File

@ -1,32 +0,0 @@
/// Container to register and look up SwiftLint rules.
public final class RuleRegistry {
private var registeredRules = [Rule.Type]()
/// Shared rule registry instance.
public static let shared = RuleRegistry()
/// Rule list associated with this registry. Lazily created, and
/// immutable once looked up.
///
/// - note: Adding registering more rules after this was first
/// accessed will not work.
public private(set) lazy var list = RuleList(rules: registeredRules)
private init() {}
/// Register rules.
///
/// - parameter rules: The rules to register.
public func register(rules: [Rule.Type]) {
registeredRules.append(contentsOf: rules)
}
/// Look up a rule for a given ID.
///
/// - parameter id: The ID for the rule to look up.
///
/// - returns: The rule matching the specified ID, if one was found.
public func rule(forID id: String) -> Rule.Type? {
return list.list[id]
}
}

View File

@ -1,34 +0,0 @@
/// A rule configuration that allows specifying the desired severity level for violations.
public struct SeverityConfiguration<Parent: Rule>: SeverityBasedRuleConfiguration, Equatable {
/// Configuration with a warning severity.
public static var error: Self { Self(.error) }
/// Configuration with an error severity.
public static var warning: Self { Self(.warning) }
public var consoleDescription: String {
return severity.rawValue
}
var severity: ViolationSeverity
public var severityConfiguration: SeverityConfiguration {
self
}
/// Create a `SeverityConfiguration` with the specified severity.
///
/// - parameter severity: The severity that should be used when emitting violations.
public init(_ severity: ViolationSeverity) {
self.severity = severity
}
public mutating func apply(configuration: Any) throws {
let configString = configuration as? String
let configDict = configuration as? [String: Any]
guard let severityString: String = configString ?? configDict?["severity"] as? String,
let severity = ViolationSeverity(rawValue: severityString.lowercased()) else {
throw Issue.unknownConfiguration(ruleID: Parent.description.identifier)
}
self.severity = severity
}
}

View File

@ -1,6 +0,0 @@
/// Interface providing access to a cache description.
public protocol CacheDescriptionProvider {
/// The cache description which will be used to determine if a previous
/// cached value is still valid given the new cache value.
var cacheDescription: String { get }
}

View File

@ -1,40 +0,0 @@
/// An interface for reporting violations as strings.
public protocol Reporter: CustomStringConvertible {
/// The unique identifier for this reporter.
static var identifier: String { get }
/// Whether or not this reporter can output incrementally as violations are found or if all violations must be
/// collected before generating the report.
static var isRealtime: Bool { get }
/// A more detailed description of the reporter's output.
static var description: String { get }
/// For CustomStringConvertible conformance.
var description: String { get }
/// Return a string with the report for the specified violations.
///
/// - parameter violations: The violations to report.
///
/// - returns: The report.
static func generateReport(_ violations: [StyleViolation]) -> String
}
public extension Reporter {
/// For CustomStringConvertible conformance.
var description: String { Self.description }
}
/// Returns the reporter with the specified identifier. Traps if the specified identifier doesn't correspond to any
/// known reporters.
///
/// - parameter identifier: The identifier corresponding to the reporter.
///
/// - returns: The reporter type.
public func reporterFrom(identifier: String) -> Reporter.Type {
guard let reporter = reportersList.first(where: { $0.identifier == identifier }) else {
queuedFatalError("No reporter with identifier '\(identifier)' available.")
}
return reporter
}

View File

@ -1,126 +0,0 @@
import Foundation
import SwiftyTextTable
/// Reports a summary table of all violations
public struct SummaryReporter: Reporter {
// MARK: - Reporter Conformance
public static let identifier = "summary"
public static let isRealtime = false
public static let description = "Reports a summary table of all violations."
public static func generateReport(_ violations: [StyleViolation]) -> String {
TextTable(violations: violations).renderWithExtraSeparator()
}
}
// MARK: - SwiftyTextTable
private extension TextTable {
// swiftlint:disable:next function_body_length
init(violations: [StyleViolation]) {
let numberOfWarningsHeader = "warnings"
let numberOfErrorsHeader = "errors"
let numberOfViolationsHeader = "total violations"
let numberOfFilesHeader = "number of files"
let columns = [
TextTableColumn(header: "rule identifier"),
TextTableColumn(header: "opt-in"),
TextTableColumn(header: "correctable"),
TextTableColumn(header: "custom"),
TextTableColumn(header: numberOfWarningsHeader),
TextTableColumn(header: numberOfErrorsHeader),
TextTableColumn(header: numberOfViolationsHeader),
TextTableColumn(header: numberOfFilesHeader)
]
self.init(columns: columns)
let ruleIdentifiersToViolationsMap = violations.group { $0.ruleIdentifier }
let sortedRuleIdentifiers = ruleIdentifiersToViolationsMap.keys.sorted {
let count1 = ruleIdentifiersToViolationsMap[$0]?.count ?? 0
let count2 = ruleIdentifiersToViolationsMap[$1]?.count ?? 0
if count1 > count2 {
return true
} else if count1 == count2 {
return $0 < $1
}
return false
}
var totalNumberOfWarnings = 0
var totalNumberOfErrors = 0
for ruleIdentifier in sortedRuleIdentifiers {
guard let ruleIdentifier = ruleIdentifiersToViolationsMap[ruleIdentifier]?.first?.ruleIdentifier else {
continue
}
let rule = RuleRegistry.shared.rule(forID: ruleIdentifier)
let violations = ruleIdentifiersToViolationsMap[ruleIdentifier]
let numberOfWarnings = violations?.filter { $0.severity == .warning }.count ?? 0
let numberOfErrors = violations?.filter { $0.severity == .error }.count ?? 0
let numberOfViolations = numberOfWarnings + numberOfErrors
totalNumberOfWarnings += numberOfWarnings
totalNumberOfErrors += numberOfErrors
let ruleViolations = ruleIdentifiersToViolationsMap[ruleIdentifier] ?? []
let numberOfFiles = Set(ruleViolations.map { $0.location.file }).count
addRow(values: [
ruleIdentifier,
rule is OptInRule.Type ? "yes" : "no",
rule is CorrectableRule.Type ? "yes" : "no",
rule == nil ? "yes" : "no",
numberOfWarnings.formattedString.leftPadded(forHeader: numberOfWarningsHeader),
numberOfErrors.formattedString.leftPadded(forHeader: numberOfErrorsHeader),
numberOfViolations.formattedString.leftPadded(forHeader: numberOfViolationsHeader),
numberOfFiles.formattedString.leftPadded(forHeader: numberOfFilesHeader)
])
}
let totalNumberOfViolations = totalNumberOfWarnings + totalNumberOfErrors
let totalNumberOfFiles = Set(violations.map { $0.location.file }).count
addRow(values: [
"Total",
"",
"",
"",
totalNumberOfWarnings.formattedString.leftPadded(forHeader: numberOfWarningsHeader),
totalNumberOfErrors.formattedString.leftPadded(forHeader: numberOfErrorsHeader),
totalNumberOfViolations.formattedString.leftPadded(forHeader: numberOfViolationsHeader),
totalNumberOfFiles.formattedString.leftPadded(forHeader: numberOfFilesHeader)
])
}
func renderWithExtraSeparator() -> String {
var output = render()
var lines = output.components(separatedBy: "\n")
if lines.count > 5, let lastLine = lines.last {
lines.insert(lastLine, at: lines.count - 2)
output = lines.joined(separator: "\n")
}
return output
}
}
private extension String {
func leftPadded(forHeader header: String) -> String {
let headerCount = header.count - self.count
if headerCount > 0 {
return String(repeating: " ", count: headerCount) + self
}
return self
}
}
private extension Int {
private static var numberFormatter: NumberFormatter = {
let numberFormatter = NumberFormatter()
numberFormatter.numberStyle = .decimal
return numberFormatter
}()
var formattedString: String {
// swiftlint:disable:next legacy_objc_type
Int.numberFormatter.string(from: NSNumber(value: self)) ?? ""
}
}

View File

@ -1,5 +0,0 @@
/// The rule list containing all available rules built into SwiftLintCore.
public let coreRules: [Rule.Type] = [
CustomRules.self,
SuperfluousDisableCommandRule.self
]

View File

@ -1,107 +0,0 @@
import SwiftSyntax
/// A specialized `ViolationsSyntaxVisitor` that tracks declared identifiers per scope while traversing the AST.
open class DeclaredIdentifiersTrackingVisitor: ViolationsSyntaxVisitor {
/// A type that remembers the declared identifers (in order) up to the current position in the code.
public typealias Scope = Stack<Set<String>>
/// The hierarchical stack of identifiers declared up to the current position in the code.
public private(set) var scope: Scope
/// Initializer.
///
/// - parameter scope: A (potentially already pre-filled) scope to collect identifers into.
public init(scope: Scope = Scope()) {
self.scope = scope
super.init(viewMode: .sourceAccurate)
}
/// Indicate whether a given identifier is in scope.
///
/// - parameter identifier: An identifier.
public func hasSeenDeclaration(for identifier: String) -> Bool {
scope.contains { $0.contains(identifier) }
}
// swiftlint:disable:next cyclomatic_complexity
override open func visit(_ node: CodeBlockItemListSyntax) -> SyntaxVisitorContinueKind {
guard let parent = node.parent, !parent.is(SourceFileSyntax.self), let grandParent = parent.parent else {
return .visitChildren
}
scope.openChildScope()
if let ifStmt = grandParent.as(IfExprSyntax.self), parent.keyPathInParent != \IfExprSyntax.elseBody {
collectIdentifiers(fromConditions: ifStmt.conditions)
} else if let whileStmt = grandParent.as(WhileStmtSyntax.self) {
collectIdentifiers(fromConditions: whileStmt.conditions)
} else if let pattern = grandParent.as(ForInStmtSyntax.self)?.pattern {
collectIdentifiers(fromPattern: pattern)
} else if let parameters = grandParent.as(FunctionDeclSyntax.self)?.signature.input.parameterList {
parameters.forEach { scope.addToCurrentScope(($0.secondName ?? $0.firstName).text) }
} else if let input = parent.as(ClosureExprSyntax.self)?.signature?.input {
switch input {
case let .input(parameters):
parameters.parameterList.forEach { scope.addToCurrentScope(($0.secondName ?? $0.firstName).text) }
case let .simpleInput(parameters):
parameters.forEach { scope.addToCurrentScope($0.name.text) }
}
} else if let switchCase = parent.as(SwitchCaseSyntax.self)?.label.as(SwitchCaseLabelSyntax.self) {
switchCase.caseItems
.compactMap { $0.pattern.as(ValueBindingPatternSyntax.self)?.valuePattern ?? $0.pattern }
.compactMap { $0.as(ExpressionPatternSyntax.self)?.expression.asFunctionCall }
.compactMap { $0.argumentList.as(TupleExprElementListSyntax.self) }
.flatMap { $0 }
.compactMap { $0.expression.as(UnresolvedPatternExprSyntax.self) }
.compactMap { $0.pattern.as(ValueBindingPatternSyntax.self)?.valuePattern ?? $0.pattern }
.compactMap { $0.as(IdentifierPatternSyntax.self) }
.forEach { scope.addToCurrentScope($0.identifier.text) }
} else if let catchClause = grandParent.as(CatchClauseSyntax.self) {
if let items = catchClause.catchItems {
items
.compactMap { $0.pattern?.as(ValueBindingPatternSyntax.self)?.valuePattern }
.forEach(collectIdentifiers(fromPattern:))
} else {
// A catch clause without explicit catch items has an implicit `error` variable in scope.
scope.addToCurrentScope("error")
}
}
return .visitChildren
}
override open func visitPost(_ node: CodeBlockItemListSyntax) {
scope.pop()
}
override open func visitPost(_ node: VariableDeclSyntax) {
if node.parent?.is(MemberDeclListItemSyntax.self) != true {
for binding in node.bindings {
collectIdentifiers(fromPattern: binding.pattern)
}
}
}
override open func visitPost(_ node: GuardStmtSyntax) {
collectIdentifiers(fromConditions: node.conditions)
}
private func collectIdentifiers(fromConditions conditions: ConditionElementListSyntax) {
conditions
.compactMap { $0.condition.as(OptionalBindingConditionSyntax.self)?.pattern }
.forEach { collectIdentifiers(fromPattern: $0) }
}
private func collectIdentifiers(fromPattern pattern: PatternSyntax) {
if let name = pattern.as(IdentifierPatternSyntax.self)?.identifier.text {
scope.addToCurrentScope(name)
}
}
}
private extension DeclaredIdentifiersTrackingVisitor.Scope {
mutating func addToCurrentScope(_ identifier: String) {
modifyLast { $0.insert(identifier) }
}
mutating func openChildScope() {
push([])
}
}

View File

@ -1,74 +0,0 @@
import SwiftSyntax
/// A SwiftSyntax `SyntaxVisitor` that produces absolute positions where violations should be reported.
open class ViolationsSyntaxVisitor: SyntaxVisitor {
/// Positions in a source file where violations should be reported.
public var violations: [ReasonedRuleViolation] = []
/// List of declaration types that shall be skipped while traversing the AST.
open var skippableDeclarations: [DeclSyntaxProtocol.Type] { [] }
override open func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == ActorDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == ClassDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == EnumDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == ExtensionDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == FunctionDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: SubscriptDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == FunctionDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == VariableDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == ProtocolDeclSyntax.self } ? .skipChildren : .visitChildren
}
override open func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind {
skippableDeclarations.contains { $0 == StructDeclSyntax.self } ? .skipChildren : .visitChildren
}
}
public extension Array where Element == DeclSyntaxProtocol.Type {
/// All visitable declaration syntax types.
static let all: Self = [
ActorDeclSyntax.self,
ClassDeclSyntax.self,
EnumDeclSyntax.self,
FunctionDeclSyntax.self,
ExtensionDeclSyntax.self,
ProtocolDeclSyntax.self,
StructDeclSyntax.self,
VariableDeclSyntax.self
]
/// Useful for class-specific checks since extensions and protocols do not allow nested classes.
static let extensionsAndProtocols: Self = [
ExtensionDeclSyntax.self,
ProtocolDeclSyntax.self
]
/// All declarations except for the specified ones.
///
/// - parameter declarations: The declarations to exclude from all declarations.
///
/// - returns: All declarations except for the specified ones.
static func allExcept(_ declarations: Element...) -> Self {
all.filter { decl in !declarations.contains { $0 == decl } }
}
}

View File

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

View File

@ -1,14 +0,0 @@
@_exported import SwiftLintBuiltInRules
@_exported import SwiftLintCore
import SwiftLintExtraRules
private let _registerAllRulesOnceImpl: Void = {
RuleRegistry.shared.register(rules: builtInRules + coreRules + extraRules())
}()
public extension RuleRegistry {
/// Register all rules. Should only be called once before any SwiftLint code is executed.
static func registerAllRulesOnce() {
_ = _registerAllRulesOnceImpl
}
}

View File

@ -1,6 +1,6 @@
import Dispatch
public extension Array where Element: Equatable {
extension Array where Element: Equatable {
/// The elements in this array, discarding duplicates after the first one.
/// Order-preserving.
var unique: [Element] {
@ -12,7 +12,7 @@ public extension Array where Element: Equatable {
}
}
public extension Array where Element: Hashable {
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.
@ -33,7 +33,7 @@ public extension Array where Element: Hashable {
}
}
public extension Array {
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.
@ -67,7 +67,8 @@ public extension Array {
///
/// - returns: The elements failing the `belongsInSecondPartition` test, followed by the elements passing the
/// `belongsInSecondPartition` test.
func partitioned(by belongsInSecondPartition: (Element) throws -> Bool) rethrows ->
@_spi(TestHelper)
public func partitioned(by belongsInSecondPartition: (Element) throws -> Bool) rethrows ->
(first: ArraySlice<Element>, second: ArraySlice<Element>) {
var copy = self
let pivot = try copy.partition(by: belongsInSecondPartition)
@ -79,7 +80,8 @@ public extension Array {
/// - 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] {
@_spi(TestHelper)
public func parallelFlatMap<T>(transform: (Element) -> [T]) -> [T] {
return parallelMap(transform: transform).flatMap { $0 }
}
@ -108,9 +110,10 @@ public extension Array {
}
}
public extension Collection {
extension Collection {
/// Whether this collection has one or more element.
var isNotEmpty: Bool {
@_spi(TestHelper)
public var isNotEmpty: Bool {
return !isEmpty
}

View File

@ -1,7 +1,7 @@
import SourceKittenFramework
import SwiftSyntax
public extension ByteCount {
extension ByteCount {
/// Converts a SwiftSyntax `AbsolutePosition` to a SourceKitten `ByteCount`.
///
/// - parameter position: The SwiftSyntax position to convert.

View File

@ -1,4 +1,3 @@
// swiftlint:disable:next blanket_disable_command
// swiftlint:disable all
// Copied from https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Windows.swift

View File

@ -47,10 +47,10 @@ extension Configuration {
Self.nestedConfigIsSelfByIdentifierLock.unlock()
}
internal static func getIsNestedConfigurationSelf(forIdentifier identifier: String) -> Bool {
internal static func getIsNestedConfigurationSelf(forIdentifier identifier: String) -> Bool? {
Self.nestedConfigIsSelfByIdentifierLock.lock()
defer { Self.nestedConfigIsSelfByIdentifierLock.unlock() }
return Self.nestedConfigIsSelfByIdentifier[identifier] ?? false
return Self.nestedConfigIsSelfByIdentifier[identifier]
}
// MARK: SwiftLint Cache (On-Disk)
@ -92,7 +92,7 @@ extension Configuration {
do {
try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
} catch {
Issue.genericWarning("Cannot create cache: " + error.localizedDescription).print()
queuedPrintError("Error while creating cache: " + error.localizedDescription)
}
return folder

View File

@ -11,7 +11,7 @@ public extension Configuration {
private let ignoreParentAndChildConfigs: Bool
private var vertices: Set<Vertex>
private var vertices: Set<Vertix>
private var edges: Set<Edge>
private var isBuilt = false
@ -19,7 +19,7 @@ public extension Configuration {
// MARK: - Initializers
internal init(commandLineChildConfigs: [String], rootDirectory: String, ignoreParentAndChildConfigs: Bool) {
let verticesArray = commandLineChildConfigs.map { config in
Vertex(string: config, rootDirectory: rootDirectory, isInitialVertex: true)
Vertix(string: config, rootDirectory: rootDirectory, isInitialVertix: true)
}
vertices = Set(verticesArray)
edges = Set(zip(verticesArray, verticesArray.dropFirst()).map { Edge(parent: $0.0, child: $0.1) })
@ -56,11 +56,11 @@ public extension Configuration {
)
}
internal func includesFile(atPath path: String) -> Bool {
guard isBuilt else { return false }
internal func includesFile(atPath path: String) -> Bool? {
guard isBuilt else { return nil }
return vertices.contains { vertex in
if case let .existing(filePath) = vertex.filePath {
return vertices.contains { vertix in
if case let .existing(filePath) = vertix.filePath {
return path == filePath
}
@ -70,19 +70,19 @@ public extension Configuration {
// MARK: Building
private mutating func build() throws {
for vertex in vertices {
try process(vertex: vertex)
for vertix in vertices {
try process(vertix: vertix)
}
isBuilt = true
}
private mutating func process(
vertex: Vertex,
vertix: Vertix,
remoteConfigTimeoutOverride: TimeInterval? = nil,
remoteConfigTimeoutIfCachedOverride: TimeInterval? = nil
) throws {
try vertex.build(
try vertix.build(
remoteConfigTimeout: remoteConfigTimeoutOverride ?? Configuration.FileGraph.defaultRemoteConfigTimeout,
remoteConfigTimeoutIfCached: remoteConfigTimeoutIfCachedOverride
?? remoteConfigTimeoutOverride ?? Configuration.FileGraph.defaultRemoteConfigTimeoutIfCached
@ -91,13 +91,13 @@ public extension Configuration {
if !ignoreParentAndChildConfigs {
try processPossibleReference(
ofType: .childConfig,
from: vertex,
from: vertix,
remoteConfigTimeoutOverride: remoteConfigTimeoutOverride,
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCachedOverride
)
try processPossibleReference(
ofType: .parentConfig,
from: vertex,
from: vertix,
remoteConfigTimeoutOverride: remoteConfigTimeoutOverride,
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCachedOverride
)
@ -106,46 +106,46 @@ public extension Configuration {
private mutating func processPossibleReference(
ofType type: EdgeType,
from vertex: Vertex,
from vertix: Vertix,
remoteConfigTimeoutOverride: TimeInterval?,
remoteConfigTimeoutIfCachedOverride: TimeInterval?
) throws {
let key = type == .childConfig ? Configuration.Key.childConfig.rawValue
: Configuration.Key.parentConfig.rawValue
if let reference = vertex.configurationDict[key] as? String {
let referencedVertex = Vertex(string: reference, rootDirectory: vertex.rootDirectory,
isInitialVertex: false)
if let reference = vertix.configurationDict[key] as? String {
let referencedVertix = Vertix(string: reference, rootDirectory: vertix.rootDirectory,
isInitialVertix: false)
// Local vertices are allowed to have local / remote references
// Remote vertices are only allowed to have remote references
if vertex.originatesFromRemote && !referencedVertex.originatesFromRemote {
throw Issue.genericWarning("Remote configs are not allowed to reference local configs.")
if vertix.originatesFromRemote && !referencedVertix.originatesFromRemote {
throw ConfigurationError.generic("Remote configs are not allowed to reference local configs.")
} else {
let existingVertex = findPossiblyExistingVertex(sameAs: referencedVertex)
let existingVertexCopy = existingVertex.map { $0.copy(withNewRootDirectory: rootDirectory) }
let existingVertix = findPossiblyExistingVertix(sameAs: referencedVertix)
let existingVertixCopy = existingVertix.map { $0.copy(withNewRootDirectory: rootDirectory) }
edges.insert(
type == .childConfig
? Edge(parent: vertex, child: existingVertexCopy ?? referencedVertex)
: Edge(parent: existingVertexCopy ?? referencedVertex, child: vertex)
? Edge(parent: vertix, child: existingVertixCopy ?? referencedVertix)
: Edge(parent: existingVertixCopy ?? referencedVertix, child: vertix)
)
if existingVertex == nil {
vertices.insert(referencedVertex)
if existingVertix == nil {
vertices.insert(referencedVertix)
// Use timeout config from vertex / parent of vertex if some
// Use timeout config from vertix / parent of vertix if some
let remoteConfigTimeout =
vertex.configurationDict[Configuration.Key.remoteConfigTimeout.rawValue]
vertix.configurationDict[Configuration.Key.remoteConfigTimeout.rawValue]
as? TimeInterval
?? remoteConfigTimeoutOverride // from vertex parent
?? remoteConfigTimeoutOverride // from vertix parent
let remoteConfigTimeoutIfCached =
vertex.configurationDict[Configuration.Key.remoteConfigTimeoutIfCached.rawValue]
vertix.configurationDict[Configuration.Key.remoteConfigTimeoutIfCached.rawValue]
as? TimeInterval
?? remoteConfigTimeoutIfCachedOverride // from vertex parent
?? remoteConfigTimeoutIfCachedOverride // from vertix parent
try process(
vertex: referencedVertex,
vertix: referencedVertix,
remoteConfigTimeoutOverride: remoteConfigTimeout,
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCached
)
@ -154,10 +154,10 @@ public extension Configuration {
}
}
private func findPossiblyExistingVertex(sameAs vertex: Vertex) -> Vertex? {
private func findPossiblyExistingVertix(sameAs vertix: Vertix) -> Vertix? {
return vertices.first {
$0.originalRemoteString != nil && $0.originalRemoteString == vertex.originalRemoteString
} ?? vertices.first { $0.filePath == vertex.filePath }
$0.originalRemoteString != nil && $0.originalRemoteString == vertix.originalRemoteString
} ?? vertices.first { $0.filePath == vertix.filePath }
}
// MARK: Validating
@ -165,11 +165,11 @@ public extension Configuration {
/// If successful, returns array of configuration dicts that represents the graph
private func validate() throws -> [(configurationDict: [String: Any], rootDirectory: String)] {
// Detect cycles via back-edge detection during DFS
func walkDown(stack: [Vertex]) throws {
func walkDown(stack: [Vertix]) throws {
// Please note that the equality check (`==`), not the identity check (`===`) is used
let children = edges.filter { $0.parent == stack.last }.map { $0.child! }
if stack.contains(where: children.contains) {
throw Issue.genericWarning("There's a cycle of child / parent config references. "
throw ConfigurationError.generic("There's a cycle of child / parent config references. "
+ "Please check the hierarchy of configuration files passed via the command line "
+ "and the childConfig / parentConfig entries within them.")
}
@ -180,36 +180,36 @@ public extension Configuration {
// Detect ambiguities
if (edges.contains { edge in edges.filter { $0.parent == edge.parent }.count > 1 }) {
throw Issue.genericWarning("There's an ambiguity in the child / parent configuration tree: "
throw ConfigurationError.generic("There's an ambiguity in the child / parent configuration tree: "
+ "More than one parent is declared for a specific configuration, "
+ "where there should only be exactly one.")
}
if (edges.contains { edge in edges.filter { $0.child == edge.child }.count > 1 }) {
throw Issue.genericWarning("There's an ambiguity in the child / parent configuration tree: "
throw ConfigurationError.generic("There's an ambiguity in the child / parent configuration tree: "
+ "More than one child is declared for a specific configuration, "
+ "where there should only be exactly one.")
}
// The graph should be like an array if validation passed -> return that array
guard
let startingVertex = (vertices.first { vertex in !edges.contains { $0.child == vertex } })
let startingVertix = (vertices.first { vertix in !edges.contains { $0.child == vertix } })
else {
guard vertices.isEmpty else {
throw Issue.genericWarning("Unknown Configuration Error")
throw ConfigurationError.generic("Unknown Configuration Error")
}
return []
}
var verticesToMerge = [startingVertex]
while let vertex = (edges.first { $0.parent == verticesToMerge.last }?.child) {
guard !verticesToMerge.contains(vertex) else {
var verticesToMerge = [startingVertix]
while let vertix = (edges.first { $0.parent == verticesToMerge.last }?.child) {
guard !verticesToMerge.contains(vertix) else {
// This shouldn't happen on a cycle free graph but let's safeguard
throw Issue.genericWarning("Unknown Configuration Error")
throw ConfigurationError.generic("Unknown Configuration Error")
}
verticesToMerge.append(vertex)
verticesToMerge.append(vertix)
}
return verticesToMerge.map {

View File

@ -7,8 +7,8 @@ internal extension Configuration.FileGraph {
case existing(path: String)
}
// MARK: - Vertex
class Vertex: Hashable {
// MARK: - Vertix
class Vertix: Hashable {
internal let originalRemoteString: String?
internal var originatesFromRemote: Bool { originalRemoteString != nil }
internal var rootDirectory: String {
@ -22,11 +22,11 @@ internal extension Configuration.FileGraph {
}
private let originalRootDirectory: String
let isInitialVertex: Bool
let isInitialVertix: Bool
private(set) var filePath: FilePath
private(set) var configurationDict: [String: Any] = [:]
init(string: String, rootDirectory: String, isInitialVertex: Bool) {
init(string: String, rootDirectory: String, isInitialVertix: Bool) {
originalRootDirectory = rootDirectory
if string.hasPrefix("http://") || string.hasPrefix("https://") {
originalRemoteString = string
@ -37,25 +37,25 @@ internal extension Configuration.FileGraph {
path: string.bridge().absolutePathRepresentation(rootDirectory: rootDirectory)
)
}
self.isInitialVertex = isInitialVertex
self.isInitialVertix = isInitialVertix
}
init(originalRemoteString: String?, originalRootDirectory: String, filePath: FilePath, isInitialVertex: Bool) {
init(originalRemoteString: String?, originalRootDirectory: String, filePath: FilePath, isInitialVertix: Bool) {
self.originalRemoteString = originalRemoteString
self.originalRootDirectory = originalRootDirectory
self.filePath = filePath
self.isInitialVertex = isInitialVertex
self.isInitialVertix = isInitialVertix
}
internal func copy(withNewRootDirectory rootDirectory: String) -> Vertex {
let vertex = Vertex(
internal func copy(withNewRootDirectory rootDirectory: String) -> Vertix {
let vertix = Vertix(
originalRemoteString: originalRemoteString,
originalRootDirectory: rootDirectory,
filePath: filePath,
isInitialVertex: isInitialVertex
isInitialVertix: isInitialVertix
)
vertex.configurationDict = configurationDict
return vertex
vertix.configurationDict = configurationDict
return vertix
}
internal func build(
@ -73,15 +73,15 @@ internal extension Configuration.FileGraph {
private func read(at path: String) throws -> String {
guard !path.isEmpty && FileManager.default.fileExists(atPath: path) else {
throw isInitialVertex ?
Issue.initialFileNotFound(path: path) :
Issue.genericWarning("File \(path) can't be found.")
throw isInitialVertix ?
ConfigurationError.initialFileNotFound(path: path) :
ConfigurationError.generic("File \(path) can't be found.")
}
return try String(contentsOfFile: path, encoding: .utf8)
}
internal static func == (lhs: Vertex, rhs: Vertex) -> Bool {
internal static func == (lhs: Vertix, rhs: Vertix) -> Bool {
return lhs.filePath == rhs.filePath
&& lhs.originalRemoteString == rhs.originalRemoteString
&& lhs.rootDirectory == rhs.rootDirectory
@ -96,8 +96,8 @@ internal extension Configuration.FileGraph {
// MARK: - Edge
struct Edge: Hashable {
var parent: Vertex!
var child: Vertex!
var parent: Vertix!
var child: Vertix!
}
// MARK: - EdgeType

View File

@ -1,11 +1,6 @@
import Foundation
extension Configuration {
public enum ExcludeBy {
case prefix
case paths(excludedPaths: [String])
}
// MARK: Lintable Paths
/// Returns the files that can be linted by SwiftLint in the specified parent path.
///
@ -17,8 +12,8 @@ extension Configuration {
///
/// - returns: Files to lint.
public func lintableFiles(inPath path: String, forceExclude: Bool,
excludeBy: ExcludeBy) -> [SwiftLintFile] {
return lintablePaths(inPath: path, forceExclude: forceExclude, excludeBy: excludeBy)
excludeByPrefix: Bool = false) -> [SwiftLintFile] {
return lintablePaths(inPath: path, forceExclude: forceExclude, excludeByPrefix: excludeByPrefix)
.compactMap(SwiftLintFile.init(pathDeferringReading:))
}
@ -35,17 +30,14 @@ extension Configuration {
internal func lintablePaths(
inPath path: String,
forceExclude: Bool,
excludeBy: ExcludeBy,
excludeByPrefix: Bool = false,
fileManager: LintableFileManager = FileManager.default
) -> [String] {
if fileManager.isFile(atPath: path) {
if path.isFile {
if forceExclude {
switch excludeBy {
case .prefix:
return filterExcludedPathsByPrefix(in: [path.absolutePathStandardized()])
case .paths(let excludedPaths):
return filterExcludedPaths(excludedPaths, in: [path.absolutePathStandardized()])
}
return excludeByPrefix
? filterExcludedPathsByPrefix(in: [path.absolutePathStandardized()])
: filterExcludedPaths(fileManager: fileManager, in: [path.absolutePathStandardized()])
}
// If path is a file and we're not forcing excludes, skip filtering with excluded/included paths
return [path]
@ -56,12 +48,9 @@ extension Configuration {
.flatMap(Glob.resolveGlob)
.parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) }
switch excludeBy {
case .prefix:
return filterExcludedPathsByPrefix(in: pathsForPath, includedPaths)
case .paths(let excludedPaths):
return filterExcludedPaths(excludedPaths, in: pathsForPath, includedPaths)
}
return excludeByPrefix
? filterExcludedPathsByPrefix(in: pathsForPath, includedPaths)
: filterExcludedPaths(fileManager: fileManager, in: pathsForPath, includedPaths)
}
/// Returns an array of file paths after removing the excluded paths as defined by this configuration.
@ -71,7 +60,7 @@ extension Configuration {
///
/// - returns: The input paths after removing the excluded paths.
public func filterExcludedPaths(
_ excludedPaths: [String],
fileManager: LintableFileManager = FileManager.default,
in paths: [String]...
) -> [String] {
let allPaths = paths.flatMap { $0 }
@ -82,6 +71,7 @@ extension Configuration {
let result = NSMutableOrderedSet(array: allPaths)
#endif
let excludedPaths = self.excludedPaths(fileManager: fileManager)
result.minusSet(Set(excludedPaths))
// swiftlint:disable:next force_cast
return result.map { $0 as! String }
@ -108,7 +98,7 @@ extension Configuration {
/// - parameter fileManager: The file manager to get child paths in a given parent location.
///
/// - returns: The expanded excluded file paths.
public func excludedPaths(fileManager: LintableFileManager = FileManager.default) -> [String] {
private func excludedPaths(fileManager: LintableFileManager) -> [String] {
return excludedPaths
.flatMap(Glob.resolveGlob)
.parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) }

View File

@ -106,7 +106,7 @@ extension Configuration {
config = self
} else if
FileManager.default.fileExists(atPath: configurationSearchPath),
!fileGraph.includesFile(atPath: configurationSearchPath)
fileGraph.includesFile(atPath: configurationSearchPath) != true
{
// Use self merged with the nested config that was found
// iff that nested config has not already been used to build the main config

View File

@ -1,3 +1,5 @@
// swiftlint:disable inclusive_language - To ease a migration from the previous `whitelist_rules`
extension Configuration {
// MARK: - Subtypes
internal enum Key: String, CaseIterable {
@ -9,8 +11,10 @@ extension Configuration {
case optInRules = "opt_in_rules"
case reporter = "reporter"
case swiftlintVersion = "swiftlint_version"
case useNestedConfigs = "use_nested_configs" // deprecated, always enabled
case warningThreshold = "warning_threshold"
case onlyRules = "only_rules"
case whitelistRules = "whitelist_rules" // deprecated in favor of onlyRules
case indentation = "indentation"
case analyzerRules = "analyzer_rules"
case allowZeroLintableFiles = "allow_zero_lintable_files"
@ -34,7 +38,7 @@ extension Configuration {
/// - parameter cachePath: The location of the persisted cache on disk.
public init(
dict: [String: Any],
ruleList: RuleList = RuleRegistry.shared.list,
ruleList: RuleList = primaryRuleList,
enableAllRules: Bool = false,
cachePath: String? = nil
) throws {
@ -44,7 +48,8 @@ extension Configuration {
let optInRules = defaultStringArray(dict[Key.optInRules.rawValue] ?? dict[Key.enabledRules.rawValue])
let disabledRules = defaultStringArray(dict[Key.disabledRules.rawValue])
let onlyRules = defaultStringArray(dict[Key.onlyRules.rawValue])
// Use either the new 'only_rules' or fallback to the deprecated 'whitelist_rules'
let onlyRules = defaultStringArray(dict[Key.onlyRules.rawValue] ?? dict[Key.whitelistRules.rawValue])
let analyzerRules = defaultStringArray(dict[Key.analyzerRules.rawValue])
Self.warnAboutInvalidKeys(configurationDictionary: dict, ruleList: ruleList)
@ -60,7 +65,7 @@ extension Configuration {
} catch let RuleListError.duplicatedConfigurations(ruleType) {
let aliases = ruleType.description.deprecatedAliases.map { "'\($0)'" }.joined(separator: ", ")
let identifier = ruleType.description.identifier
throw Issue.genericWarning(
throw ConfigurationError.generic(
"Multiple configurations found for '\(identifier)'. Check for any aliases: \(aliases)."
)
}
@ -102,7 +107,8 @@ extension Configuration {
if let indentationStyle = Self.IndentationStyle(rawIndentation) {
return indentationStyle
}
Issue.invalidConfiguration(ruleID: Key.indentation.rawValue).print()
queuedPrintError("Invalid configuration for '\(Key.indentation)'. Falling back to default.")
return .default
}
@ -118,7 +124,23 @@ extension Configuration {
) {
// Deprecation warning for "enabled_rules"
if dict[Key.enabledRules.rawValue] != nil {
Issue.renamedIdentifier(old: Key.enabledRules.rawValue, new: Key.optInRules.rawValue).print()
queuedPrintError("warning: '\(Key.enabledRules.rawValue)' has been renamed to " +
"'\(Key.optInRules.rawValue)' and will be completely removed in a " +
"future release.")
}
// Deprecation warning for "use_nested_configs"
if dict[Key.useNestedConfigs.rawValue] != nil {
queuedPrintError("warning: Support for '\(Key.useNestedConfigs.rawValue)' has " +
"been deprecated and its value is now ignored. Nested configuration files are " +
"now always considered.")
}
// Deprecation warning for "whitelist_rules"
if dict[Key.whitelistRules.rawValue] != nil {
queuedPrintError("'\(Key.whitelistRules.rawValue)' has been renamed to " +
"'\(Key.onlyRules.rawValue)' and will be completely removed in a " +
"future release.")
}
// Deprecation warning for rules
@ -132,7 +154,10 @@ extension Configuration {
}
for (deprecatedIdentifier, identifier) in deprecatedUsages {
Issue.renamedIdentifier(old: deprecatedIdentifier, new: identifier).print()
queuedPrintError(
"warning: '\(deprecatedIdentifier)' rule has been renamed to '\(identifier)' and will be "
+ "completely removed in a future release."
)
}
}
@ -140,7 +165,7 @@ extension Configuration {
// Log an error when supplying invalid keys in the configuration dictionary
let invalidKeys = Set(dict.keys).subtracting(validKeys(ruleList: ruleList))
if invalidKeys.isNotEmpty {
Issue.invalidConfigurationKeys(invalidKeys.sorted()).print()
queuedPrintError("warning: Configuration contains invalid keys:\n\(invalidKeys)")
}
}
@ -155,7 +180,7 @@ extension Configuration {
continue
}
let message = "Found a configuration for '\(identifier)' rule"
let message = "warning: Found a configuration for '\(identifier)' rule"
switch rulesMode {
case .allEnabled:
@ -163,14 +188,17 @@ extension Configuration {
case .only(let onlyRules):
if Set(onlyRules).isDisjoint(with: rule.description.allIdentifiers) {
Issue.genericWarning("\(message), but it is not present on '\(Key.onlyRules.rawValue)'.").print()
queuedPrintError("\(message), but it is not present on " +
"'\(Key.onlyRules.rawValue)'.")
}
case let .default(disabled: disabledRules, optIn: optInRules):
if rule is OptInRule.Type, Set(optInRules).isDisjoint(with: rule.description.allIdentifiers) {
Issue.genericWarning("\(message), but it is not enabled on '\(Key.optInRules.rawValue)'.").print()
queuedPrintError("\(message), but it is not enabled on " +
"'\(Key.optInRules.rawValue)'.")
} else if Set(disabledRules).isSuperset(of: rule.description.allIdentifiers) {
Issue.genericWarning("\(message), but it is disabled on '\(Key.disabledRules.rawValue)'.").print()
queuedPrintError("\(message), but it is disabled on " +
"'\(Key.disabledRules.rawValue)'.")
}
}
}
@ -183,12 +211,10 @@ extension Configuration {
Set(analyzerRules).intersection(optInRules)
.sorted()
.forEach {
Issue.genericWarning(
"""
'\($0)' should be listed in the 'analyzer_rules' configuration section \
for more clarity as it is only run by 'swiftlint analyze'.
"""
).print()
queuedPrintError("""
warning: '\($0)' should be listed in the 'analyzer_rules' configuration section \
for more clarity as it is only run by 'swiftlint analyze'
""")
}
}
}

View File

@ -65,7 +65,7 @@ internal extension Configuration.FileGraph.FilePath {
// Handle wrong url format
guard let url = URL(string: urlString) else {
throw Issue.genericWarning("Invalid configuration entry: \"\(urlString)\" isn't a valid url.")
throw ConfigurationError.generic("Invalid configuration entry: \"\(urlString)\" isn't a valid url.")
}
// Load from url
@ -126,7 +126,7 @@ internal extension Configuration.FileGraph.FilePath {
self = .existing(path: cachedFilePath)
return cachedFilePath
} else {
throw Issue.genericWarning(
throw ConfigurationError.generic(
"No internet connectivity: Unable to load remote config from \"\(urlString)\". "
+ "Also didn't found cached version to fallback to."
)
@ -155,12 +155,12 @@ internal extension Configuration.FileGraph.FilePath {
return cachedFilePath
} else {
if taskDone {
throw Issue.genericWarning(
throw ConfigurationError.generic(
"Unable to load remote config from \"\(urlString)\". "
+ "Also didn't found cached version to fallback to."
)
} else {
throw Issue.genericWarning(
throw ConfigurationError.generic(
"Timeout (\(timeout) sec): Unable to load remote config from \"\(urlString)\". "
+ "Also didn't found cached version to fallback to."
)
@ -170,13 +170,11 @@ internal extension Configuration.FileGraph.FilePath {
private mutating func handleFileWriteFailure(urlString: String, cachedFilePath: String?) throws -> String {
if let cachedFilePath {
queuedPrintError(
"warning: Unable to cache remote config from \"\(urlString)\". Using cached version as a fallback."
)
queuedPrintError("Unable to cache remote config from \"\(urlString)\". Using cached version as a fallback.")
self = .existing(path: cachedFilePath)
return cachedFilePath
} else {
throw Issue.genericWarning(
throw ConfigurationError.generic(
"Unable to cache remote config from \"\(urlString)\". Also didn't found cached version to fallback to."
)
}
@ -261,7 +259,7 @@ internal extension Configuration.FileGraph.FilePath {
contents: Data(newGitignoreAppendix.utf8),
attributes: [:]
) else {
throw Issue.genericWarning("Issue maintaining remote config cache.")
throw ConfigurationError.generic("Issue maintaining remote config cache.")
}
} else {
var contents = try String(contentsOfFile: Configuration.FileGraph.FilePath.gitignorePath, encoding: .utf8)

View File

@ -41,7 +41,7 @@ public extension Configuration {
let duplicateRules = identifiers.reduce(into: [String: Int]()) { $0[$1, default: 0] += 1 }
.filter { $0.1 > 1 }
for duplicateRule in duplicateRules {
Issue.listedMultipleTime(ruleID: duplicateRule.0, times: duplicateRule.1).print()
queuedPrintError("warning: '\(duplicateRule.0)' is listed \(duplicateRule.1) times")
}
}
}
@ -50,7 +50,7 @@ public extension Configuration {
self = .allEnabled
} else if onlyRules.isNotEmpty {
if disabledRules.isNotEmpty || optInRules.isNotEmpty {
throw Issue.genericWarning(
throw ConfigurationError.generic(
"'\(Configuration.Key.disabledRules.rawValue)' or " +
"'\(Configuration.Key.optInRules.rawValue)' cannot be used in combination " +
"with '\(Configuration.Key.onlyRules.rawValue)'"
@ -61,19 +61,8 @@ public extension Configuration {
self = .only(Set(onlyRules + analyzerRules))
} else {
warnAboutDuplicates(in: disabledRules)
let effectiveOptInRules: [String]
if optInRules.contains(RuleIdentifier.all.stringRepresentation) {
let allOptInRules = RuleRegistry.shared.list.list.compactMap { ruleID, ruleType in
ruleType is OptInRule.Type && !(ruleType is AnalyzerRule.Type) ? ruleID : nil
}
effectiveOptInRules = Array(Set(allOptInRules + optInRules))
} else {
effectiveOptInRules = optInRules
}
warnAboutDuplicates(in: effectiveOptInRules + analyzerRules)
self = .default(disabled: Set(disabledRules), optIn: Set(effectiveOptInRules + analyzerRules))
warnAboutDuplicates(in: optInRules + analyzerRules)
self = .default(disabled: Set(disabledRules), optIn: Set(optInRules + analyzerRules))
}
}

View File

@ -32,7 +32,7 @@ internal extension Configuration {
if let cachedResultingRules { return cachedResultingRules }
// Calculate value
let customRulesFilter: (RegexConfiguration<CustomRules>) -> (Bool)
let customRulesFilter: (RegexConfiguration) -> (Bool)
var resultingRules = [Rule]()
switch mode {
case .allEnabled:
@ -49,7 +49,7 @@ internal extension Configuration {
case var .default(disabledRuleIdentifiers, optInRuleIdentifiers):
customRulesFilter = { !disabledRuleIdentifiers.contains($0.identifier) }
disabledRuleIdentifiers = validate(ruleIds: disabledRuleIdentifiers, valid: validRuleIdentifiers)
optInRuleIdentifiers = validate(optInRuleIds: optInRuleIdentifiers, valid: validRuleIdentifiers)
optInRuleIdentifiers = validate(ruleIds: optInRuleIdentifiers, valid: validRuleIdentifiers)
resultingRules = allRulesWrapped.filter { tuple in
let id = type(of: tuple.rule).description.identifier
return !disabledRuleIdentifiers.contains(id)
@ -113,10 +113,6 @@ internal extension Configuration {
}
// MARK: - Methods: Validation
private func validate(optInRuleIds: Set<String>, valid: Set<String>) -> Set<String> {
validate(ruleIds: optInRuleIds, valid: valid.union([RuleIdentifier.all.stringRepresentation]))
}
private func validate(ruleIds: Set<String>, valid: Set<String>, silent: Bool = false) -> Set<String> {
// Process invalid rule identifiers
if !silent {
@ -224,12 +220,12 @@ internal extension Configuration {
validRuleIdentifiers: Set<String>
) -> RulesMode {
let childDisabled = child.validate(ruleIds: childDisabled, valid: validRuleIdentifiers)
let childOptIn = child.validate(optInRuleIds: childOptIn, valid: validRuleIdentifiers)
let childOptIn = child.validate(ruleIds: childOptIn, valid: validRuleIdentifiers)
switch mode { // Switch parent's mode. Child is in default mode.
case var .default(disabled, optIn):
disabled = validate(ruleIds: disabled, valid: validRuleIdentifiers)
optIn = child.validate(optInRuleIds: optIn, valid: validRuleIdentifiers)
optIn = child.validate(ruleIds: optIn, valid: validRuleIdentifiers)
// Only use parent disabled / optIn if child config doesn't tell the opposite
return .default(

View File

@ -21,7 +21,7 @@ public struct SourceKittenDictionary {
/// Creates a SourceKitten dictionary given a `Dictionary<String, SourceKitRepresentable>` input.
///
/// - parameter value: The input dictionary/
public init(_ value: [String: SourceKitRepresentable]) {
init(_ value: [String: SourceKitRepresentable]) {
self.value = value
let substructure = value["key.substructure"] as? [SourceKitRepresentable] ?? []
@ -37,123 +37,124 @@ public struct SourceKittenDictionary {
}
/// Body length
public var bodyLength: ByteCount? {
var bodyLength: ByteCount? {
return (value["key.bodylength"] as? Int64).map(ByteCount.init)
}
/// Body offset.
public var bodyOffset: ByteCount? {
var bodyOffset: ByteCount? {
return (value["key.bodyoffset"] as? Int64).map(ByteCount.init)
}
/// Body byte range.
public var bodyByteRange: ByteRange? {
var bodyByteRange: ByteRange? {
guard let offset = bodyOffset, let length = bodyLength else { return nil }
return ByteRange(location: offset, length: length)
}
/// Kind.
public var kind: String? {
var kind: String? {
return value["key.kind"] as? String
}
/// Length.
public var length: ByteCount? {
var length: ByteCount? {
return (value["key.length"] as? Int64).map(ByteCount.init)
}
/// Name.
public var name: String? {
var name: String? {
return value["key.name"] as? String
}
/// Name length.
public var nameLength: ByteCount? {
var nameLength: ByteCount? {
return (value["key.namelength"] as? Int64).map(ByteCount.init)
}
/// Name offset.
public var nameOffset: ByteCount? {
var nameOffset: ByteCount? {
return (value["key.nameoffset"] as? Int64).map(ByteCount.init)
}
/// Byte range of name.
public var nameByteRange: ByteRange? {
var nameByteRange: ByteRange? {
guard let offset = nameOffset, let length = nameLength else { return nil }
return ByteRange(location: offset, length: length)
}
/// Offset.
public var offset: ByteCount? {
var offset: ByteCount? {
return (value["key.offset"] as? Int64).map(ByteCount.init)
}
/// Returns byte range starting from `offset` with `length` bytes
public var byteRange: ByteRange? {
var byteRange: ByteRange? {
guard let offset, let length else { return nil }
return ByteRange(location: offset, length: length)
}
/// Setter accessibility.
public var setterAccessibility: String? {
var setterAccessibility: String? {
return value["key.setter_accessibility"] as? String
}
/// Type name.
public var typeName: String? {
var typeName: String? {
return value["key.typename"] as? String
}
/// Documentation length.
public var docLength: ByteCount? {
var docLength: ByteCount? {
return (value["key.doclength"] as? Int64).flatMap(ByteCount.init)
}
/// The attribute for this dictionary, as returned by SourceKit.
public var attribute: String? {
var attribute: String? {
return value["key.attribute"] as? String
}
/// Module name in `@import` expressions.
public var moduleName: String? {
var moduleName: String? {
return value["key.modulename"] as? String
}
/// The line number for this declaration.
public var line: Int64? {
var line: Int64? {
return value["key.line"] as? Int64
}
/// The column number for this declaration.
public var column: Int64? {
var column: Int64? {
return value["key.column"] as? Int64
}
/// The `SwiftDeclarationAttributeKind` values associated with this dictionary.
public var enclosedSwiftAttributes: [SwiftDeclarationAttributeKind] {
var enclosedSwiftAttributes: [SwiftDeclarationAttributeKind] {
return swiftAttributes.compactMap { $0.attribute }
.compactMap(SwiftDeclarationAttributeKind.init(rawValue:))
}
/// The fully preserved SourceKitten dictionaries for all the attributes associated with this dictionary.
public var swiftAttributes: [SourceKittenDictionary] {
var swiftAttributes: [SourceKittenDictionary] {
let array = value["key.attributes"] as? [SourceKitRepresentable] ?? []
return array.compactMap { $0 as? [String: SourceKitRepresentable] }
let dictionaries = array.compactMap { $0 as? [String: SourceKitRepresentable] }
.map(Self.init)
return dictionaries
}
public var elements: [SourceKittenDictionary] {
var elements: [SourceKittenDictionary] {
let elements = value["key.elements"] as? [SourceKitRepresentable] ?? []
return elements.compactMap { $0 as? [String: SourceKitRepresentable] }
.map(Self.init)
}
public var entities: [SourceKittenDictionary] {
var entities: [SourceKittenDictionary] {
let entities = value["key.entities"] as? [SourceKitRepresentable] ?? []
return entities.compactMap { $0 as? [String: SourceKitRepresentable] }
.map(Self.init)
}
public var enclosedVarParameters: [SourceKittenDictionary] {
var enclosedVarParameters: [SourceKittenDictionary] {
return substructure.flatMap { subDict -> [SourceKittenDictionary] in
if subDict.declarationKind == .varParameter {
return [subDict]
@ -166,7 +167,7 @@ public struct SourceKittenDictionary {
}
}
public var enclosedArguments: [SourceKittenDictionary] {
var enclosedArguments: [SourceKittenDictionary] {
return substructure.flatMap { subDict -> [SourceKittenDictionary] in
guard subDict.expressionKind == .argument else {
return []
@ -176,7 +177,7 @@ public struct SourceKittenDictionary {
}
}
public var inheritedTypes: [String] {
var inheritedTypes: [String] {
let array = value["key.inheritedtypes"] as? [SourceKitRepresentable] ?? []
return array.compactMap { ($0 as? [String: String]).flatMap { $0["key.name"] } }
}
@ -189,7 +190,7 @@ extension SourceKittenDictionary {
/// - parameter traverseBlock: block that will be called for each substructure in the dictionary.
///
/// - returns: The list of substructure dictionaries with updated values from the traverse block.
public func traverseDepthFirst<T>(traverseBlock: (SourceKittenDictionary) -> [T]?) -> [T] {
func traverseDepthFirst<T>(traverseBlock: (SourceKittenDictionary) -> [T]?) -> [T] {
var result: [T] = []
traverseDepthFirst(collectingValuesInto: &result, traverseBlock: traverseBlock)
return result
@ -212,7 +213,7 @@ extension SourceKittenDictionary {
/// - parameter traverseBlock: block that will be called for each entity in the dictionary.
///
/// - returns: The list of entity dictionaries with updated values from the traverse block.
public func traverseEntitiesDepthFirst<T>(traverseBlock: (SourceKittenDictionary) -> T?) -> [T] {
func traverseEntitiesDepthFirst<T>(traverseBlock: (SourceKittenDictionary) -> T?) -> [T] {
var result: [T] = []
traverseEntitiesDepthFirst(collectingValuesInto: &result, traverseBlock: traverseBlock)
return result
@ -230,7 +231,7 @@ extension SourceKittenDictionary {
}
}
public extension Dictionary where Key == Example {
extension Dictionary where Key == Example {
/// Returns a dictionary with SwiftLint violation markers () removed from keys.
///
/// - returns: A new `Dictionary`.

View File

@ -19,13 +19,6 @@ public protocol LintableFileManager {
///
/// - returns: A date, if one was determined.
func modificationDate(forFileAtPath path: String) -> Date?
/// Returns true if a file (but not a directory) exists at the specified path.
///
/// - parameter path: The path that should be checked to see if it is a file.
///
/// - returns: true if the specified path is a file.
func isFile(atPath path: String) -> Bool
}
extension FileManager: LintableFileManager {
@ -49,8 +42,4 @@ extension FileManager: LintableFileManager {
public func modificationDate(forFileAtPath path: String) -> Date? {
return (try? attributesOfItem(atPath: path))?[.modificationDate] as? Date
}
public func isFile(atPath path: String) -> Bool {
path.isFile
}
}

View File

@ -14,8 +14,8 @@ private struct RegexCacheKey: Hashable {
}
}
public extension NSRegularExpression {
static func cached(pattern: String, options: Options? = nil) throws -> NSRegularExpression {
extension NSRegularExpression {
internal static func cached(pattern: String, options: Options? = nil) throws -> NSRegularExpression {
let options = options ?? [.anchorsMatchLines, .dotMatchesLineSeparators]
let key = RegexCacheKey(pattern: pattern, options: options)
regexCacheLock.lock()
@ -29,19 +29,19 @@ public extension NSRegularExpression {
return result
}
func matches(in stringView: StringView,
options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
internal func matches(in stringView: StringView,
options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
return matches(in: stringView.string, options: options, range: stringView.range)
}
func matches(in stringView: StringView,
options: NSRegularExpression.MatchingOptions = [],
range: NSRange) -> [NSTextCheckingResult] {
internal func matches(in stringView: StringView,
options: NSRegularExpression.MatchingOptions = [],
range: NSRange) -> [NSTextCheckingResult] {
return matches(in: stringView.string, options: options, range: range)
}
func matches(in file: SwiftLintFile,
options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
internal func matches(in file: SwiftLintFile,
options: NSRegularExpression.MatchingOptions = []) -> [NSTextCheckingResult] {
return matches(in: file.stringView.string, options: options, range: file.stringView.range)
}
}

View File

@ -1,4 +1,4 @@
public extension RandomAccessCollection where Index == Int {
extension RandomAccessCollection where Index == Int {
/// Returns the first index in which an element of the collection satisfies the given predicate.
/// The collection assumed to be sorted. If collection is not have sorted values the result is undefined.
///

View File

@ -1,7 +1,7 @@
import Foundation
import SourceKittenFramework
public extension Request {
extension Request {
static let disableSourceKit = ProcessInfo.processInfo.environment["SWIFTLINT_DISABLE_SOURCEKIT"] != nil
func sendIfNotDisabled() throws -> [String: SourceKitRepresentable] {
@ -11,7 +11,7 @@ public extension Request {
return try send()
}
static func cursorInfoWithoutSymbolGraph(file: String, offset: ByteCount, arguments: [String]) -> Request {
static func cursorInfo(file: String, offset: ByteCount, arguments: [String]) -> Request {
.customRequest(request: [
"key.request": UID("source.request.cursorinfo"),
"key.name": file,

View File

@ -1,13 +1,13 @@
import SourceKittenFramework
public extension SourceKittenDictionary {
extension SourceKittenDictionary {
/// Returns array of tuples containing "key.kind" and "byteRange" from Structure
/// that contains the byte offset. Returns all kinds if no parameter specified.
///
/// - parameter byteOffset: Int?
///
/// - returns: The kinds and byte ranges.
func kinds(forByteOffset byteOffset: ByteCount? = nil)
internal func kinds(forByteOffset byteOffset: ByteCount? = nil)
-> [(kind: String, byteRange: ByteRange)] {
var results = [(kind: String, byteRange: ByteRange)]()
@ -27,7 +27,7 @@ public extension SourceKittenDictionary {
return results
}
func structures(forByteOffset byteOffset: ByteCount) -> [SourceKittenDictionary] {
internal func structures(forByteOffset byteOffset: ByteCount) -> [SourceKittenDictionary] {
var results = [SourceKittenDictionary]()
func parse(_ dictionary: SourceKittenDictionary) {

View File

@ -1,6 +1,6 @@
import SwiftSyntax
public extension SourceRange {
extension SourceRange {
/// Check if a position is contained within this range.
///
/// - parameter position: The position to check.
@ -8,8 +8,8 @@ public extension SourceRange {
///
/// - returns: Whether the specified position is contained within this range.
func contains(_ position: AbsolutePosition, locationConverter: SourceLocationConverter) -> Bool {
let startPosition = locationConverter.position(ofLine: start.line, column: start.column)
let endPosition = locationConverter.position(ofLine: end.line, column: end.column)
let startPosition = locationConverter.position(ofLine: start.line ?? 1, column: start.column ?? 1)
let endPosition = locationConverter.position(ofLine: end.line ?? 1, column: end.column ?? 1)
return startPosition <= position && position <= endPosition
}
}

View File

@ -1,8 +1,8 @@
import Foundation
import SourceKittenFramework
public extension String {
func hasTrailingWhitespace() -> Bool {
extension String {
internal func hasTrailingWhitespace() -> Bool {
if isEmpty {
return false
}
@ -14,14 +14,22 @@ public extension String {
return false
}
func isUppercase() -> Bool {
internal func isUppercase() -> Bool {
return self == uppercased()
}
func isLowercase() -> Bool {
internal func isLowercase() -> Bool {
return self == lowercased()
}
internal func nameStrippingLeadingUnderscoreIfPrivate(_ dict: SourceKittenDictionary) -> String {
if let acl = dict.accessibility,
acl.isPrivate && first == "_" {
return String(self[index(after: startIndex)...])
}
return self
}
private subscript (range: Range<Int>) -> String {
let nsrange = NSRange(location: range.lowerBound,
length: range.upperBound - range.lowerBound)
@ -31,21 +39,21 @@ public extension String {
queuedFatalError("invalid range")
}
func substring(from: Int, length: Int? = nil) -> String {
internal func substring(from: Int, length: Int? = nil) -> String {
if let length {
return self[from..<from + length]
}
return String(self[index(startIndex, offsetBy: from, limitedBy: endIndex)!...])
}
func lastIndex(of search: String) -> Int? {
internal func lastIndex(of search: String) -> Int? {
if let range = range(of: search, options: [.literal, .backwards]) {
return distance(from: startIndex, to: range.lowerBound)
}
return nil
}
func nsrangeToIndexRange(_ nsrange: NSRange) -> Range<Index>? {
internal func nsrangeToIndexRange(_ nsrange: NSRange) -> Range<Index>? {
guard nsrange.location != NSNotFound else {
return nil
}
@ -62,18 +70,18 @@ public extension String {
return fromIndex..<toIndex
}
var fullNSRange: NSRange {
internal var fullNSRange: NSRange {
return NSRange(location: 0, length: utf16.count)
}
/// Returns a new string, converting the path to a canonical absolute path.
///
/// - returns: A new `String`.
func absolutePathStandardized() -> String {
public func absolutePathStandardized() -> String {
return bridge().absolutePathRepresentation().bridge().standardizingPath
}
var isFile: Bool {
internal var isFile: Bool {
if self.isEmpty {
return false
}
@ -87,14 +95,14 @@ public extension String {
/// Count the number of occurrences of the given character in `self`
/// - Parameter character: Character to count
/// - Returns: Number of times `character` occurs in `self`
func countOccurrences(of character: Character) -> Int {
public func countOccurrences(of character: Character) -> Int {
return self.reduce(0, {
$1 == character ? $0 + 1 : $0
})
}
/// If self is a path, this method can be used to get a path expression relative to a root directory
func path(relativeTo rootDirectory: String) -> String {
public func path(relativeTo rootDirectory: String) -> String {
let normalizedRootDir = rootDirectory.bridge().standardizingPath
let normalizedSelf = bridge().standardizingPath
if normalizedRootDir.isEmpty {
@ -115,7 +123,7 @@ public extension String {
}
}
func deletingPrefix(_ prefix: String) -> String {
internal func deletingPrefix(_ prefix: String) -> String {
guard hasPrefix(prefix) else { return self }
return String(dropFirst(prefix.count))
}

View File

@ -1,6 +1,6 @@
import SourceKittenFramework
public extension StringView {
extension StringView {
/// Converts a line and column position in a code snippet to a byte offset.
/// - Parameters:
/// - line: Line in code snippet

View File

@ -2,7 +2,7 @@ import Foundation
import SourceKittenFramework
import SwiftSyntax
public extension StringView {
extension StringView {
/// Converts two absolute positions from SwiftSyntax to a valid `NSRange` if possible.
///
/// - parameter start: Starting position.

View File

@ -1,6 +1,6 @@
import SourceKittenFramework
public extension SwiftDeclarationAttributeKind {
extension SwiftDeclarationAttributeKind {
static var attributesRequiringFoundation: Set<SwiftDeclarationAttributeKind> {
return [
.objc,
@ -24,7 +24,7 @@ public extension SwiftDeclarationAttributeKind {
case `dynamic`
case atPrefixed
public init?(rawAttribute: String) {
init?(rawAttribute: String) {
let allModifierGroups: Set<SwiftDeclarationAttributeKind.ModifierGroup> = [
.acl, .setterACL, .mutators, .override, .owned, .atPrefixed, .dynamic, .final, .typeMethods,
.required, .convenience, .lazy
@ -40,7 +40,7 @@ public extension SwiftDeclarationAttributeKind {
}
}
public var swiftDeclarationAttributeKinds: Set<SwiftDeclarationAttributeKind> {
var swiftDeclarationAttributeKinds: Set<SwiftDeclarationAttributeKind> {
switch self {
case .acl:
return [
@ -94,7 +94,7 @@ public extension SwiftDeclarationAttributeKind {
}
}
public var debugDescription: String {
var debugDescription: String {
return self.rawValue
}
}

View File

@ -1,7 +1,7 @@
import SourceKittenFramework
public extension SwiftDeclarationKind {
static let variableKinds: Set<SwiftDeclarationKind> = [
extension SwiftDeclarationKind {
internal static let variableKinds: Set<SwiftDeclarationKind> = [
.varClass,
.varGlobal,
.varInstance,
@ -10,7 +10,7 @@ public extension SwiftDeclarationKind {
.varStatic
]
static let functionKinds: Set<SwiftDeclarationKind> = [
internal static let functionKinds: Set<SwiftDeclarationKind> = [
.functionAccessorAddress,
.functionAccessorDidset,
.functionAccessorGetter,
@ -27,7 +27,7 @@ public extension SwiftDeclarationKind {
.functionSubscript
]
static let typeKinds: Set<SwiftDeclarationKind> = [
internal static let typeKinds: Set<SwiftDeclarationKind> = [
.class,
.struct,
.typealias,
@ -35,7 +35,7 @@ public extension SwiftDeclarationKind {
.enum
]
static let extensionKinds: Set<SwiftDeclarationKind> = [
internal static let extensionKinds: Set<SwiftDeclarationKind> = [
.extension,
.extensionClass,
.extensionEnum,

View File

@ -1,6 +1,6 @@
import SwiftSyntax
public extension SwiftLintFile {
extension SwiftLintFile {
/// This function determines if given a scope with a left/right brace, such as a function, closure, type, etc, how
/// many lines the "body" spans when you ignore lines only containing comments and/or whitespace.
///
@ -47,11 +47,10 @@ public extension SwiftLintFile {
let sourceRange = token
.trimmed
.sourceRange(converter: locationConverter)
let startLine = sourceRange.start.line
let endLine = sourceRange.end.line
let startLine = sourceRange.start.line!
let endLine = sourceRange.end.line!
linesWithTokens.formUnion(startLine...endLine)
} else {
let line = locationConverter.location(for: token.positionAfterSkippingLeadingTrivia).line
} else if let line = locationConverter.location(for: token.positionAfterSkippingLeadingTrivia).line {
linesWithTokens.insert(line)
}
}

View File

@ -2,8 +2,8 @@
import Darwin
#endif
import Foundation
import IDEUtils
import SourceKittenFramework
import SwiftIDEUtils
import SwiftOperators
import SwiftParser
import SwiftParserDiagnostics
@ -101,7 +101,7 @@ extension SwiftLintFile {
return id
}
public var sourcekitdFailed: Bool {
internal var sourcekitdFailed: Bool {
get {
return responseCache.get(self) == nil
}
@ -123,7 +123,7 @@ extension SwiftLintFile {
}
}
public var parserDiagnostics: [String]? {
internal var parserDiagnostics: [String]? {
if parserDiagnosticsDisabledForTests {
return nil
}
@ -133,9 +133,9 @@ extension SwiftLintFile {
.map(\.message)
}
public var linesWithTokens: Set<Int> { linesWithTokensCache.get(self) }
internal var linesWithTokens: Set<Int> { linesWithTokensCache.get(self) }
public var structureDictionary: SourceKittenDictionary {
internal var structureDictionary: SourceKittenDictionary {
guard let structureDictionary = structureDictionaryCache.get(self) else {
if let handler = assertHandler {
handler()
@ -146,9 +146,9 @@ extension SwiftLintFile {
return structureDictionary
}
public var syntaxClassifications: SyntaxClassifications { syntaxClassificationsCache.get(self) }
internal var syntaxClassifications: SyntaxClassifications { syntaxClassificationsCache.get(self) }
public var syntaxMap: SwiftLintSyntaxMap {
internal var syntaxMap: SwiftLintSyntaxMap {
guard let syntaxMap = syntaxMapCache.get(self) else {
if let handler = assertHandler {
handler()
@ -159,17 +159,15 @@ extension SwiftLintFile {
return syntaxMap
}
public var syntaxTree: SourceFileSyntax { syntaxTreeCache.get(self) }
internal var syntaxTree: SourceFileSyntax { syntaxTreeCache.get(self) }
public var foldedSyntaxTree: SourceFileSyntax? { foldedSyntaxTreeCache.get(self) }
internal var foldedSyntaxTree: SourceFileSyntax? { foldedSyntaxTreeCache.get(self) }
public var locationConverter: SourceLocationConverter { locationConverterCache.get(self) }
internal var locationConverter: SourceLocationConverter { locationConverterCache.get(self) }
public var commands: [Command] { commandsCache.get(self).filter { $0.isValid } }
internal var commands: [Command] { commandsCache.get(self) }
public var invalidCommands: [Command] { commandsCache.get(self).filter { !$0.isValid } }
public var syntaxTokensByLines: [[SwiftLintSyntaxToken]] {
internal var syntaxTokensByLines: [[SwiftLintSyntaxToken]] {
guard let syntaxTokensByLines = syntaxTokensByLinesCache.get(self) else {
if let handler = assertHandler {
handler()
@ -180,7 +178,7 @@ extension SwiftLintFile {
return syntaxTokensByLines
}
public var syntaxKindsByLines: [[SourceKittenFramework.SyntaxKind]] {
internal var syntaxKindsByLines: [[SourceKittenFramework.SyntaxKind]] {
guard let syntaxKindsByLines = syntaxKindsByLinesCache.get(self) else {
if let handler = assertHandler {
handler()

View File

@ -1,8 +1,8 @@
import Foundation
import SourceKittenFramework
public func regex(_ pattern: String,
options: NSRegularExpression.Options? = nil) -> NSRegularExpression {
internal func regex(_ pattern: String,
options: NSRegularExpression.Options? = nil) -> NSRegularExpression {
// all patterns used for regular expressions in SwiftLint are string literals which have been
// confirmed to work, so it's ok to force-try here.
@ -12,7 +12,7 @@ public func regex(_ pattern: String,
}
extension SwiftLintFile {
public func regions(restrictingRuleIdentifiers: Set<RuleIdentifier>? = nil) -> [Region] {
internal func regions(restrictingRuleIdentifiers: Set<RuleIdentifier>? = nil) -> [Region] {
var regions = [Region]()
var disabledRules = Set<RuleIdentifier>()
let commands: [Command]
@ -31,9 +31,6 @@ extension SwiftLintFile {
case .enable:
disabledRules.subtract(command.ruleIdentifiers)
case .invalid:
break
}
let start = Location(file: path, line: command.line, character: command.character)
@ -57,7 +54,7 @@ extension SwiftLintFile {
return regions
}
public func commands(in range: NSRange? = nil) -> [Command] {
internal func commands(in range: NSRange? = nil) -> [Command] {
guard let range else {
return commands
.flatMap { $0.expand() }
@ -93,14 +90,14 @@ extension SwiftLintFile {
return Location(file: path, line: nextLine, character: nextCharacter)
}
public func match(pattern: String, with syntaxKinds: [SyntaxKind], range: NSRange? = nil) -> [NSRange] {
internal func match(pattern: String, with syntaxKinds: [SyntaxKind], range: NSRange? = nil) -> [NSRange] {
return match(pattern: pattern, range: range)
.filter { $0.1 == syntaxKinds }
.map { $0.0 }
}
public func matchesAndTokens(matching pattern: String,
range: NSRange? = nil) -> [(NSTextCheckingResult, [SwiftLintSyntaxToken])] {
internal func matchesAndTokens(matching pattern: String,
range: NSRange? = nil) -> [(NSTextCheckingResult, [SwiftLintSyntaxToken])] {
let contents = stringView
let range = range ?? contents.range
let syntax = syntaxMap
@ -110,25 +107,25 @@ extension SwiftLintFile {
}
}
public func matchesAndSyntaxKinds(matching pattern: String,
range: NSRange? = nil) -> [(NSTextCheckingResult, [SyntaxKind])] {
internal func matchesAndSyntaxKinds(matching pattern: String,
range: NSRange? = nil) -> [(NSTextCheckingResult, [SyntaxKind])] {
return matchesAndTokens(matching: pattern, range: range).map { textCheckingResult, tokens in
(textCheckingResult, tokens.kinds)
}
}
public func rangesAndTokens(matching pattern: String,
range: NSRange? = nil) -> [(NSRange, [SwiftLintSyntaxToken])] {
internal func rangesAndTokens(matching pattern: String,
range: NSRange? = nil) -> [(NSRange, [SwiftLintSyntaxToken])] {
return matchesAndTokens(matching: pattern, range: range).map { ($0.0.range, $0.1) }
}
public func match(pattern: String, range: NSRange? = nil, captureGroup: Int = 0) -> [(NSRange, [SyntaxKind])] {
internal func match(pattern: String, range: NSRange? = nil, captureGroup: Int = 0) -> [(NSRange, [SyntaxKind])] {
return matchesAndSyntaxKinds(matching: pattern, range: range).map { textCheckingResult, syntaxKinds in
(textCheckingResult.range(at: captureGroup), syntaxKinds)
}
}
public func swiftDeclarationKindsByLine() -> [[SwiftDeclarationKind]]? {
internal func swiftDeclarationKindsByLine() -> [[SwiftDeclarationKind]]? {
if sourcekitdFailed {
return nil
}
@ -151,7 +148,7 @@ extension SwiftLintFile {
return results
}
public func syntaxTokensByLine() -> [[SwiftLintSyntaxToken]]? {
internal func syntaxTokensByLine() -> [[SwiftLintSyntaxToken]]? {
if sourcekitdFailed {
return nil
}
@ -180,7 +177,7 @@ extension SwiftLintFile {
return results
}
public func syntaxKindsByLine() -> [[SyntaxKind]]? {
internal func syntaxKindsByLine() -> [[SyntaxKind]]? {
guard !sourcekitdFailed, let tokens = syntaxTokensByLine() else {
return nil
}
@ -199,22 +196,22 @@ extension SwiftLintFile {
- returns: An array of [NSRange] objects consisting of regex matches inside
file contents.
*/
public func match(pattern: String,
excludingSyntaxKinds syntaxKinds: Set<SyntaxKind>,
range: NSRange? = nil,
captureGroup: Int = 0) -> [NSRange] {
internal func match(pattern: String,
excludingSyntaxKinds syntaxKinds: Set<SyntaxKind>,
range: NSRange? = nil,
captureGroup: Int = 0) -> [NSRange] {
return match(pattern: pattern, range: range, captureGroup: captureGroup)
.filter { syntaxKinds.isDisjoint(with: $0.1) }
.map { $0.0 }
}
public typealias MatchMapping = (NSTextCheckingResult) -> NSRange
internal typealias MatchMapping = (NSTextCheckingResult) -> NSRange
public func match(pattern: String,
range: NSRange? = nil,
excludingSyntaxKinds: Set<SyntaxKind>,
excludingPattern: String,
exclusionMapping: MatchMapping = { $0.range }) -> [NSRange] {
internal func match(pattern: String,
range: NSRange? = nil,
excludingSyntaxKinds: Set<SyntaxKind>,
excludingPattern: String,
exclusionMapping: MatchMapping = { $0.range }) -> [NSRange] {
let matches = match(pattern: pattern, excludingSyntaxKinds: excludingSyntaxKinds)
if matches.isEmpty {
return []
@ -225,7 +222,7 @@ extension SwiftLintFile {
return matches.filter { !$0.intersects(exclusionRanges) }
}
public func append(_ string: String) {
internal func append(_ string: String) {
guard string.isNotEmpty else {
return
}
@ -245,7 +242,7 @@ extension SwiftLintFile {
invalidateCache()
}
public func write<S: StringProtocol>(_ string: S) {
internal func write<S: StringProtocol>(_ string: S) {
guard string != contents else {
return
}
@ -267,22 +264,23 @@ extension SwiftLintFile {
invalidateCache()
}
public func ruleEnabled(violatingRanges: [NSRange], for rule: Rule) -> [NSRange] {
internal func ruleEnabled(violatingRanges: [NSRange], for rule: Rule) -> [NSRange] {
let fileRegions = regions()
if fileRegions.isEmpty { return violatingRanges }
return violatingRanges.filter { range in
let violatingRanges = violatingRanges.filter { range in
let region = fileRegions.first {
$0.contains(Location(file: self, characterOffset: range.location))
}
return region?.isRuleEnabled(rule) ?? true
}
return violatingRanges
}
public func ruleEnabled(violatingRange: NSRange, for rule: Rule) -> NSRange? {
internal func ruleEnabled(violatingRange: NSRange, for rule: Rule) -> NSRange? {
return ruleEnabled(violatingRanges: [violatingRange], for: rule).first
}
public func isACL(token: SwiftLintSyntaxToken) -> Bool {
internal func isACL(token: SwiftLintSyntaxToken) -> Bool {
guard token.kind == .attributeBuiltin else {
return false
}
@ -291,7 +289,7 @@ extension SwiftLintFile {
return aclString.flatMap(AccessControlLevel.init(description:)) != nil
}
public func contents(for token: SwiftLintSyntaxToken) -> String? {
internal func contents(for token: SwiftLintSyntaxToken) -> String? {
return stringView.substringWithByteRange(token.range)
}
}

View File

@ -3,10 +3,10 @@ import SourceKittenFramework
import SwiftSyntax
// workaround for https://bugs.swift.org/browse/SR-10121 so we can use `Self` in a closure
public protocol SwiftLintSyntaxVisitor: SyntaxVisitor {}
protocol SwiftLintSyntaxVisitor: SyntaxVisitor {}
extension SyntaxVisitor: SwiftLintSyntaxVisitor {}
public extension SwiftLintSyntaxVisitor {
extension SwiftLintSyntaxVisitor {
func walk<T, SyntaxType: SyntaxProtocol>(tree: SyntaxType, handler: (Self) -> T) -> T {
#if DEBUG
// workaround for stack overflow when running in debug
@ -44,7 +44,7 @@ public extension SwiftLintSyntaxVisitor {
}
}
public extension SyntaxProtocol {
extension SyntaxProtocol {
func windowsOfThreeTokens() -> [(TokenSyntax, TokenSyntax, TokenSyntax)] {
Array(tokens(viewMode: .sourceAccurate))
.windows(ofCount: 3)
@ -61,7 +61,7 @@ public extension SyntaxProtocol {
}
}
public extension AbsolutePosition {
extension AbsolutePosition {
func isContainedIn(regions: [SourceRange], locationConverter: SourceLocationConverter) -> Bool {
regions.contains { region in
region.contains(self, locationConverter: locationConverter)
@ -69,13 +69,13 @@ public extension AbsolutePosition {
}
}
public extension ByteSourceRange {
extension ByteSourceRange {
func toSourceKittenByteRange() -> ByteRange {
ByteRange(location: ByteCount(offset), length: ByteCount(length))
}
}
public extension ClassDeclSyntax {
extension ClassDeclSyntax {
func isXCTestCase(_ testParentClasses: Set<String>) -> Bool {
guard let inheritanceList = inheritanceClause?.inheritedTypeCollection else {
return false
@ -85,7 +85,7 @@ public extension ClassDeclSyntax {
}
}
public extension ExprSyntax {
extension ExprSyntax {
var asFunctionCall: FunctionCallExprSyntax? {
if let functionCall = self.as(FunctionCallExprSyntax.self) {
return functionCall
@ -99,19 +99,19 @@ public extension ExprSyntax {
}
}
public extension StringLiteralExprSyntax {
extension StringLiteralExprSyntax {
var isEmptyString: Bool {
segments.onlyElement?.contentLength == .zero
}
}
public extension TokenKind {
extension TokenKind {
var isEqualityComparison: Bool {
self == .binaryOperator("==") || self == .binaryOperator("!=")
}
}
public extension ModifierListSyntax? {
extension ModifierListSyntax? {
var containsLazy: Bool {
contains(tokenKind: .keyword(.lazy))
}
@ -147,6 +147,10 @@ public extension ModifierListSyntax? {
}
}
var isFinal: Bool {
contains(tokenKind: .keyword(.final))
}
private func contains(tokenKind: TokenKind) -> Bool {
guard let modifiers = self else {
return false
@ -156,26 +160,26 @@ public extension ModifierListSyntax? {
}
}
public extension AttributeSyntax {
extension AttributeSyntax {
var attributeNameText: String {
attributeName.as(SimpleTypeIdentifierSyntax.self)?.name.text ??
attributeName.description
}
}
public extension AttributeListSyntax? {
extension AttributeListSyntax? {
func contains(attributeNamed attributeName: String) -> Bool {
self?.contains { $0.as(AttributeSyntax.self)?.attributeNameText == attributeName } == true
}
}
public extension TokenKind {
extension TokenKind {
var isUnavailableKeyword: Bool {
self == .keyword(.unavailable) || self == .identifier("unavailable")
}
}
public extension VariableDeclSyntax {
extension VariableDeclSyntax {
var isIBOutlet: Bool {
attributes.contains(attributeNamed: "IBOutlet")
}
@ -216,7 +220,7 @@ public extension EnumDeclSyntax {
}
}
public extension FunctionDeclSyntax {
extension FunctionDeclSyntax {
var isIBAction: Bool {
attributes.contains(attributeNamed: "IBAction")
}
@ -227,7 +231,7 @@ public extension FunctionDeclSyntax {
name += "("
let params = signature.input.parameterList.compactMap { param in
param.firstName.text.appending(":")
(param.firstName ?? param.secondName)?.text.appending(":")
}
name += params.joined()
@ -247,7 +251,7 @@ public extension FunctionDeclSyntax {
}
}
public extension AccessorBlockSyntax {
extension AccessorBlockSyntax {
var getAccessor: AccessorDeclSyntax? {
accessors.first { accessor in
accessor.accessorKind.tokenKind == .keyword(.get)
@ -269,7 +273,7 @@ public extension AccessorBlockSyntax {
}
}
public extension TypeInheritanceClauseSyntax? {
extension TypeInheritanceClauseSyntax? {
func containsInheritedType(inheritedTypes: Set<String>) -> Bool {
self?.inheritedTypeCollection.contains { elem in
guard let simpleType = elem.typeName.as(SimpleTypeIdentifierSyntax.self) else {
@ -281,7 +285,7 @@ public extension TypeInheritanceClauseSyntax? {
}
}
public extension Trivia {
extension Trivia {
func containsNewlines() -> Bool {
contains { piece in
if case .newlines = piece {
@ -295,64 +299,28 @@ public extension Trivia {
var isSingleSpace: Bool {
self == .spaces(1)
}
var withFirstEmptyLineRemoved: Trivia {
if let index = firstIndex(where: \.isNewline), index < endIndex {
return Trivia(pieces: dropFirst(index + 1))
}
return self
}
var withoutTrailingIndentation: Trivia {
Trivia(pieces: reversed().drop(while: \.isHorizontalWhitespace).reversed())
}
}
public extension TriviaPiece {
var isHorizontalWhitespace: Bool {
switch self {
case .spaces, .tabs:
return true
default:
return false
}
}
}
public extension IntegerLiteralExprSyntax {
extension IntegerLiteralExprSyntax {
var isZero: Bool {
guard case let .integerLiteral(number) = digits.tokenKind else {
return false
}
return number.isZero
}
}
public extension FloatLiteralExprSyntax {
extension FloatLiteralExprSyntax {
var isZero: Bool {
guard case let .floatingLiteral(number) = floatingDigits.tokenKind else {
return false
}
return number.isZero
}
}
public extension IdentifierExprSyntax {
var isSelf: Bool {
identifier.text == "self"
}
}
public extension ClosureCaptureItemSyntax {
var capturesSelf: Bool {
expression.as(IdentifierExprSyntax.self)?.isSelf == true
}
var capturesWeakly: Bool {
specifier?.specifier.text == "weak"
}
}
private extension String {
var isZero: Bool {
if self == "0" { // fast path

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