Compare commits
No commits in common. "main" and "0.50.1" have entirely different histories.
|
@ -1 +0,0 @@
|
||||||
.build
|
|
6
.bazelrc
6
.bazelrc
|
@ -1,14 +1,8 @@
|
||||||
common --enable_bzlmod
|
|
||||||
|
|
||||||
try-import %workspace%/ci.bazelrc
|
try-import %workspace%/ci.bazelrc
|
||||||
try-import %workspace%/user.bazelrc
|
try-import %workspace%/user.bazelrc
|
||||||
|
|
||||||
build --macos_minimum_os=12.0 --host_macos_minimum_os=12.0
|
build --macos_minimum_os=12.0 --host_macos_minimum_os=12.0
|
||||||
build --disk_cache=~/.bazel_cache
|
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 \
|
build:release \
|
||||||
--compilation_mode=opt \
|
--compilation_mode=opt \
|
||||||
|
|
|
@ -1 +1 @@
|
||||||
6.2.0
|
5.3.2
|
||||||
|
|
|
@ -1,3 +0,0 @@
|
||||||
fixedReleaser:
|
|
||||||
login: jpsim
|
|
||||||
email: jp@jpsim.com
|
|
|
@ -1,15 +0,0 @@
|
||||||
{
|
|
||||||
"homepage": "https://github.com/realm/SwiftLint",
|
|
||||||
"maintainers": [
|
|
||||||
{
|
|
||||||
"email": "jp@jpsim.com",
|
|
||||||
"github": "jpsim",
|
|
||||||
"name": "JP Simard"
|
|
||||||
}
|
|
||||||
],
|
|
||||||
"repository": [
|
|
||||||
"github:realm/SwiftLint"
|
|
||||||
],
|
|
||||||
"versions": [],
|
|
||||||
"yanked_versions": {}
|
|
||||||
}
|
|
|
@ -1,24 +0,0 @@
|
||||||
shell_commands: &shell_commands
|
|
||||||
- "echo --- Downloading and extracting Swift $SWIFT_VERSION to $SWIFT_HOME"
|
|
||||||
- "mkdir $SWIFT_HOME"
|
|
||||||
- "curl https://download.swift.org/swift-${SWIFT_VERSION}-release/ubuntu2004/swift-${SWIFT_VERSION}-RELEASE/swift-${SWIFT_VERSION}-RELEASE-ubuntu20.04.tar.gz | tar xvz --strip-components=1 -C $SWIFT_HOME"
|
|
||||||
|
|
||||||
tasks:
|
|
||||||
verify_targets_linux:
|
|
||||||
name: Verify targets (Linux)
|
|
||||||
platform: ubuntu2004
|
|
||||||
environment:
|
|
||||||
CC: "clang"
|
|
||||||
SWIFT_VERSION: "5.7.2"
|
|
||||||
SWIFT_HOME: "$HOME/swift-$SWIFT_VERSION"
|
|
||||||
PATH: "$PATH:$SWIFT_HOME/usr/bin"
|
|
||||||
shell_commands: *shell_commands
|
|
||||||
build_flags:
|
|
||||||
- "--action_env=PATH"
|
|
||||||
build_targets:
|
|
||||||
- '@SwiftLint//:swiftlint'
|
|
||||||
verify_targets_macos:
|
|
||||||
name: Verify targets (macOS)
|
|
||||||
platform: macos
|
|
||||||
build_targets:
|
|
||||||
- '@SwiftLint//:swiftlint'
|
|
|
@ -1,5 +0,0 @@
|
||||||
{
|
|
||||||
"url": "https://github.com/realm/SwiftLint/releases/download/{TAG}/bazel.tar.gz",
|
|
||||||
"integrity": "",
|
|
||||||
"strip_prefix": ""
|
|
||||||
}
|
|
|
@ -11,19 +11,23 @@ steps:
|
||||||
- swift test --parallel -Xswiftc -DDISABLE_FOCUSED_EXAMPLES
|
- swift test --parallel -Xswiftc -DDISABLE_FOCUSED_EXAMPLES
|
||||||
- label: "Danger"
|
- label: "Danger"
|
||||||
commands:
|
commands:
|
||||||
- echo "--- Install Bundler"
|
- echo "--- Build Danger"
|
||||||
- gem install bundler
|
- bazel build //tools:danger
|
||||||
- echo "--- Bundle Install"
|
|
||||||
- bundle install
|
|
||||||
- echo "+++ Run Danger"
|
- echo "+++ Run Danger"
|
||||||
- bundle exec danger --verbose
|
- ./bazel-bin/tools/danger --verbose
|
||||||
|
- label: "Analyze"
|
||||||
|
commands:
|
||||||
|
- echo "+++ Analyze"
|
||||||
|
- bazel test -c opt --test_output=streamed --test_timeout=1800 --spawn_strategy=local analyze
|
||||||
- label: "TSan Tests"
|
- label: "TSan Tests"
|
||||||
commands:
|
commands:
|
||||||
- echo "+++ Test"
|
- echo "+++ Test"
|
||||||
- bazel test --test_output=errors --build_tests_only --features=tsan --test_timeout=1000 //Tests/...
|
- bazel test --test_output=streamed --build_tests_only --features=tsan --test_timeout=1000 //Tests/...
|
||||||
- label: "Sourcery"
|
- label: "TSan Runs"
|
||||||
commands:
|
commands:
|
||||||
- echo "+++ Run Sourcery"
|
- echo "--- Build"
|
||||||
- make --always-make sourcery
|
- bazel build --config=release --features=tsan swiftlint
|
||||||
- echo "+++ Diff Files"
|
- echo "+++ Pre-cache SwiftLint Run"
|
||||||
- git diff --quiet HEAD
|
- ./bazel-bin/swiftlint --progress --lenient
|
||||||
|
- echo "+++ Post-cache SwiftLint Run"
|
||||||
|
- ./bazel-bin/swiftlint --progress --lenient
|
||||||
|
|
|
@ -9,10 +9,10 @@ on:
|
||||||
|
|
||||||
jobs:
|
jobs:
|
||||||
build:
|
build:
|
||||||
runs-on: ubuntu-20.04
|
runs-on: ubuntu-latest
|
||||||
|
|
||||||
steps:
|
steps:
|
||||||
- uses: actions/checkout@v3
|
- uses: actions/checkout@v2
|
||||||
|
|
||||||
- name: Extract DOCKER_TAG using tag name
|
- name: Extract DOCKER_TAG using tag name
|
||||||
if: startsWith(github.ref, 'refs/tags/')
|
if: startsWith(github.ref, 'refs/tags/')
|
||||||
|
|
|
@ -0,0 +1,36 @@
|
||||||
|
name: update_swift_syntax
|
||||||
|
|
||||||
|
on:
|
||||||
|
workflow_dispatch:
|
||||||
|
schedule:
|
||||||
|
# Mondays at 1pm UTC (9am EDT)
|
||||||
|
- cron: "0 13 * * 1"
|
||||||
|
|
||||||
|
jobs:
|
||||||
|
update_swift_syntax:
|
||||||
|
runs-on: ubuntu-latest
|
||||||
|
steps:
|
||||||
|
- name: Checkout source
|
||||||
|
uses: actions/checkout@v2
|
||||||
|
- name: Update SwiftSyntax
|
||||||
|
id: update-swift-syntax
|
||||||
|
run: ./tools/update-swift-syntax.sh
|
||||||
|
env:
|
||||||
|
GITHUB_TOKEN: ${{ secrets.GITHUB_TOKEN }}
|
||||||
|
- name: Check for changes
|
||||||
|
id: state
|
||||||
|
run: |
|
||||||
|
if ! git diff-index --quiet HEAD --; then
|
||||||
|
echo "dirty=true" >> $GITHUB_OUTPUT
|
||||||
|
fi
|
||||||
|
- name: Create PR
|
||||||
|
if: steps.state.outputs.dirty == 'true'
|
||||||
|
uses: peter-evans/create-pull-request@923ad837f191474af6b1721408744feb989a4c27
|
||||||
|
with:
|
||||||
|
title: Update SwiftSyntax
|
||||||
|
body: |
|
||||||
|
Diff: https://github.com/apple/swift-syntax/compare/${{ steps.update-swift-syntax.outputs.old_commit }}...${{ steps.update-swift-syntax.outputs.new_commit }}
|
||||||
|
commit-message: Update SwiftSyntax
|
||||||
|
delete-branch: true
|
||||||
|
branch: update-swift-syntax
|
||||||
|
branch-suffix: timestamp
|
|
@ -1,5 +1,6 @@
|
||||||
# Xcode
|
# Xcode
|
||||||
#
|
#
|
||||||
|
# gitignore contributors: remember to update Global/Xcode.gitignore, Objective-C.gitignore & Swift.gitignore
|
||||||
|
|
||||||
## Build generated
|
## Build generated
|
||||||
build/
|
build/
|
||||||
|
@ -21,7 +22,6 @@ xcuserdata
|
||||||
*.moved-aside
|
*.moved-aside
|
||||||
*.xcuserstate
|
*.xcuserstate
|
||||||
*.xcscmblueprint
|
*.xcscmblueprint
|
||||||
default.profraw
|
|
||||||
|
|
||||||
## Obj-C/Swift specific
|
## Obj-C/Swift specific
|
||||||
*.hmap
|
*.hmap
|
||||||
|
|
|
@ -1,4 +1,4 @@
|
||||||
module: SwiftLintCore
|
module: SwiftLintFramework
|
||||||
author: JP Simard, SwiftLint Contributors
|
author: JP Simard, SwiftLint Contributors
|
||||||
author_url: https://jpsim.com
|
author_url: https://jpsim.com
|
||||||
root_url: https://realm.github.io/SwiftLint/
|
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
|
swift_build_tool: spm
|
||||||
theme: fullwidth
|
theme: fullwidth
|
||||||
clean: true
|
clean: true
|
||||||
copyright: '© 2023 [JP Simard](https://jpsim.com) under MIT.'
|
copyright: '© 2022 [JP Simard](https://jpsim.com) under MIT.'
|
||||||
|
|
||||||
documentation: rule_docs/*.md
|
documentation: rule_docs/*.md
|
||||||
hide_unlisted_documentation: true
|
hide_unlisted_documentation: true
|
||||||
custom_categories_unlisted_prefix: ''
|
custom_categories_unlisted_prefix: ''
|
||||||
exclude:
|
exclude:
|
||||||
# TODO: Document extensions
|
- Source/SwiftLintFramework/Rules/**/*.swift
|
||||||
- Source/SwiftLintCore/Extensions/*.swift
|
|
||||||
custom_categories:
|
custom_categories:
|
||||||
- name: Rules
|
- name: Rules
|
||||||
children:
|
children:
|
||||||
|
|
|
@ -1,14 +1,12 @@
|
||||||
@testable import SwiftLintBuiltInRules
|
@testable import SwiftLintFramework
|
||||||
@_spi(TestHelper)
|
|
||||||
@testable import SwiftLintCore
|
|
||||||
import SwiftLintTestHelpers
|
import SwiftLintTestHelpers
|
||||||
|
import XCTest
|
||||||
|
|
||||||
// swiftlint:disable:next blanket_disable_command
|
|
||||||
// swiftlint:disable file_length single_test_class type_name
|
// swiftlint:disable file_length single_test_class type_name
|
||||||
|
|
||||||
{% for rule in types.structs %}
|
{% for rule in types.structs %}
|
||||||
{% if rule.name|hasSuffix:"Rule" %}
|
{% if rule.name|hasSuffix:"Rule" %}
|
||||||
class {{ rule.name }}GeneratedTests: SwiftLintTestCase {
|
class {{ rule.name }}GeneratedTests: XCTestCase {
|
||||||
func testWithDefaultConfiguration() {
|
func testWithDefaultConfiguration() {
|
||||||
verifyRule({{ rule.name }}.description)
|
verifyRule({{ rule.name }}.description)
|
||||||
}
|
}
|
||||||
|
|
|
@ -1,5 +1,4 @@
|
||||||
|
|
||||||
/// The rule list containing all available rules built into SwiftLint.
|
/// The rule list containing all available rules built into SwiftLint.
|
||||||
public let builtInRules: [Rule.Type] = [
|
public let primaryRuleList = RuleList(rules: [
|
||||||
{% for rule in types.structs where rule.name|hasSuffix:"Rule" or rule.name|hasSuffix:"Rules" %} {{ rule.name }}.self{% if not forloop.last %},{% endif %}
|
{% for rule in types.structs where rule.name|hasSuffix:"Rule" or rule.name|hasSuffix:"Rules" %} {{ rule.name }}.self{% if not forloop.last %},{% endif %}
|
||||||
{% endfor %}]
|
{% endfor %}] + extraRules())
|
|
@ -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 %}
|
|
||||||
]
|
|
131
.swiftlint.yml
131
.swiftlint.yml
|
@ -8,55 +8,75 @@ analyzer_rules:
|
||||||
- unused_declaration
|
- unused_declaration
|
||||||
- unused_import
|
- unused_import
|
||||||
opt_in_rules:
|
opt_in_rules:
|
||||||
- all
|
- array_init
|
||||||
disabled_rules:
|
- attributes
|
||||||
- anonymous_argument_in_multiline_closure
|
- closure_end_indentation
|
||||||
- anyobject_protocol
|
- closure_spacing
|
||||||
- closure_body_length
|
- collection_alignment
|
||||||
- conditional_returns_on_newline
|
- contains_over_filter_count
|
||||||
- convenience_type
|
- contains_over_filter_is_empty
|
||||||
- discouraged_optional_collection
|
- contains_over_first_not_nil
|
||||||
- explicit_acl
|
- contains_over_range_nil_comparison
|
||||||
- explicit_enum_raw_value
|
- discouraged_none_name
|
||||||
- explicit_top_level_acl
|
- discouraged_object_literal
|
||||||
- explicit_type_interface
|
- empty_collection_literal
|
||||||
- file_types_order
|
- empty_count
|
||||||
- force_unwrapping
|
- empty_string
|
||||||
- function_default_parameter_at_end
|
- empty_xctest_method
|
||||||
- implicit_return
|
- enum_case_associated_values_count
|
||||||
- implicitly_unwrapped_optional
|
- explicit_init
|
||||||
- indentation_width
|
- extension_access_modifier
|
||||||
- inert_defer
|
- fallthrough
|
||||||
- missing_docs
|
- fatal_error_message
|
||||||
- multiline_arguments
|
- file_header
|
||||||
- multiline_arguments_brackets
|
- file_name
|
||||||
- multiline_function_chains
|
- first_where
|
||||||
- multiline_literal_brackets
|
- flatmap_over_map_reduce
|
||||||
- multiline_parameters
|
- identical_operands
|
||||||
- multiline_parameters_brackets
|
- joined_default_parameter
|
||||||
- no_extension_access_modifier
|
- last_where
|
||||||
- no_fallthrough_only
|
- legacy_multiple
|
||||||
- no_grouping_extension
|
- literal_expression_end_indentation
|
||||||
- no_magic_numbers
|
- local_doc_comment
|
||||||
- prefer_nimble
|
- lower_acl_than_parent
|
||||||
- prefer_self_in_static_references
|
- modifier_order
|
||||||
- prefixed_toplevel_constant
|
- nimble_operator
|
||||||
- redundant_self_in_closure
|
- nslocalizedstring_key
|
||||||
- required_deinit
|
- number_separator
|
||||||
- self_binding
|
- object_literal
|
||||||
- sorted_enum_cases
|
- operator_usage_whitespace
|
||||||
- strict_fileprivate
|
- overridden_super_call
|
||||||
- superfluous_else
|
- override_in_extension
|
||||||
- switch_case_on_newline
|
- pattern_matching_keywords
|
||||||
- todo
|
- prefer_self_type_over_type_of_self
|
||||||
- trailing_closure
|
- private_action
|
||||||
- type_contents_order
|
- private_outlet
|
||||||
- unused_capture_list
|
- prohibited_interface_builder
|
||||||
- vertical_whitespace_between_cases
|
- prohibited_super_call
|
||||||
|
- quick_discouraged_call
|
||||||
|
- quick_discouraged_focused_test
|
||||||
|
- quick_discouraged_pending_test
|
||||||
|
- reduce_into
|
||||||
|
- redundant_nil_coalescing
|
||||||
|
- redundant_type_annotation
|
||||||
|
- return_value_from_void_function
|
||||||
|
- single_test_class
|
||||||
|
- sorted_first_last
|
||||||
|
- sorted_imports
|
||||||
|
- static_operator
|
||||||
|
- strong_iboutlet
|
||||||
|
- test_case_accessibility
|
||||||
|
- toggle_bool
|
||||||
|
- unavailable_function
|
||||||
|
- unneeded_parentheses_in_closure_argument
|
||||||
|
- unowned_variable_capture
|
||||||
|
- untyped_error_in_catch
|
||||||
|
- vertical_parameter_alignment_on_call
|
||||||
|
- vertical_whitespace_closing_braces
|
||||||
|
- vertical_whitespace_opening_braces
|
||||||
|
- xct_specific_matcher
|
||||||
|
- yoda_condition
|
||||||
|
|
||||||
attributes:
|
|
||||||
always_on_line_above:
|
|
||||||
- "@OptionGroup"
|
|
||||||
identifier_name:
|
identifier_name:
|
||||||
excluded:
|
excluded:
|
||||||
- id
|
- id
|
||||||
|
@ -65,27 +85,19 @@ number_separator:
|
||||||
minimum_length: 5
|
minimum_length: 5
|
||||||
file_name:
|
file_name:
|
||||||
excluded:
|
excluded:
|
||||||
- Exports.swift
|
|
||||||
- GeneratedTests.swift
|
|
||||||
- SwiftSyntax+SwiftLint.swift
|
- SwiftSyntax+SwiftLint.swift
|
||||||
|
- GeneratedTests.swift
|
||||||
- TestHelpers.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
|
function_body_length: 60
|
||||||
type_body_length: 400
|
type_body_length: 400
|
||||||
|
|
||||||
custom_rules:
|
custom_rules:
|
||||||
rule_id:
|
rule_id:
|
||||||
included: Source/SwiftLintBuiltInRules/Rules/.+/\w+\.swift
|
included: Source/SwiftLintFramework/Rules/.+/\w+\.swift
|
||||||
name: Rule ID
|
name: Rule ID
|
||||||
message: Rule IDs must be all lowercase, snake case and not end with `rule`
|
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
|
severity: error
|
||||||
fatal_error:
|
fatal_error:
|
||||||
name: Fatal Error
|
name: Fatal Error
|
||||||
|
@ -105,4 +117,3 @@ custom_rules:
|
||||||
unused_import:
|
unused_import:
|
||||||
always_keep_imports:
|
always_keep_imports:
|
||||||
- SwiftSyntaxBuilder # we can't detect uses of string interpolation of swift syntax nodes
|
- 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
76
BUILD
|
@ -5,68 +5,29 @@ load(
|
||||||
"swift_library",
|
"swift_library",
|
||||||
)
|
)
|
||||||
load(
|
load(
|
||||||
"@rules_xcodeproj//xcodeproj:defs.bzl",
|
"@com_github_buildbuddy_io_rules_xcodeproj//xcodeproj:defs.bzl",
|
||||||
"xcode_schemes",
|
"xcode_schemes",
|
||||||
"xcodeproj",
|
"xcodeproj",
|
||||||
)
|
)
|
||||||
|
|
||||||
# Targets
|
# 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(
|
swift_library(
|
||||||
name = "SwiftLintFramework",
|
name = "SwiftLintFramework",
|
||||||
srcs = glob(
|
srcs = glob(
|
||||||
["Source/SwiftLintFramework/**/*.swift"],
|
["Source/SwiftLintFramework/**/*.swift"],
|
||||||
),
|
exclude = ["Source/SwiftLintFramework/Rules/ExcludedFromBazel/ExtraRules.swift"],
|
||||||
|
) + ["@swiftlint_extra_rules//:extra_rules"],
|
||||||
module_name = "SwiftLintFramework",
|
module_name = "SwiftLintFramework",
|
||||||
visibility = ["//visibility:public"],
|
visibility = ["//visibility:public"],
|
||||||
deps = [
|
deps = [
|
||||||
":SwiftLintBuiltInRules",
|
"@com_github_jpsim_sourcekitten//:SourceKittenFramework",
|
||||||
":SwiftLintCore",
|
"@com_github_apple_swift_syntax//:optlibs",
|
||||||
":SwiftLintExtraRules",
|
"@sourcekitten_com_github_jpsim_yams//:Yams",
|
||||||
],
|
] + select({
|
||||||
|
"@platforms//os:linux": ["@com_github_krzyzanowskim_cryptoswift//:CryptoSwift"],
|
||||||
|
"//conditions:default": [],
|
||||||
|
}),
|
||||||
)
|
)
|
||||||
|
|
||||||
swift_library(
|
swift_library(
|
||||||
|
@ -102,21 +63,6 @@ apple_universal_binary(
|
||||||
visibility = ["//visibility:public"],
|
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
|
# Linting
|
||||||
|
|
||||||
filegroup(
|
filegroup(
|
||||||
|
@ -135,8 +81,6 @@ filegroup(
|
||||||
srcs = [
|
srcs = [
|
||||||
"BUILD",
|
"BUILD",
|
||||||
"LICENSE",
|
"LICENSE",
|
||||||
"MODULE.bazel",
|
|
||||||
"//:DyldWarningWorkaroundSources",
|
|
||||||
"//:LintInputs",
|
"//:LintInputs",
|
||||||
"//Tests:BUILD",
|
"//Tests:BUILD",
|
||||||
"//bazel:release_files",
|
"//bazel:release_files",
|
||||||
|
|
494
CHANGELOG.md
494
CHANGELOG.md
|
@ -1,497 +1,3 @@
|
||||||
## 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
|
|
||||||
|
|
||||||
#### Breaking
|
|
||||||
|
|
||||||
* Deprecate the `unused_capture_list` rule in favor of the Swift compiler
|
|
||||||
warning. At the same time, make it an opt-in rule.
|
|
||||||
[Cyberbeni](https://github.com/Cyberbeni)
|
|
||||||
[#4656](https://github.com/realm/SwiftLint/issues/4656)
|
|
||||||
|
|
||||||
* Deprecate the `inert_defer` rule in favor of the Swift compiler warning.
|
|
||||||
At the same time, make it an opt-in rule.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4615](https://github.com/realm/SwiftLint/issues/4615)
|
|
||||||
|
|
||||||
* Interpret strings in `excluded` option of `identifier_name`,
|
|
||||||
`type_name` and `generic_type_name` rules as regular expression. Existing
|
|
||||||
configurations should remain working without notice as long as they don't
|
|
||||||
contain characters that must be escaped in regular expression.
|
|
||||||
[Moly](https://github.com/kyounh12)
|
|
||||||
[#4655](https://github.com/realm/SwiftLint/pull/4655)
|
|
||||||
|
|
||||||
#### Experimental
|
|
||||||
|
|
||||||
* None.
|
|
||||||
|
|
||||||
#### Enhancements
|
|
||||||
|
|
||||||
* Add `duplicate_conditions` rule which warns when a condition is duplicated
|
|
||||||
in separate branches of the same branching statement (if-else, or switch).
|
|
||||||
[1in1](https://github.com/1in1)
|
|
||||||
[#4666](https://github.com/realm/SwiftLint/issues/4666)
|
|
||||||
|
|
||||||
* Add local links to rule descriptions to every rule listed
|
|
||||||
in `Rule Directory.md`.
|
|
||||||
[kattouf](https://github.com/kattouf)
|
|
||||||
|
|
||||||
* Make forceExclude work with directly specified files.
|
|
||||||
[jimmya](https://github.com/jimmya)
|
|
||||||
[#4609](https://github.com/realm/SwiftLint/issues/4609)
|
|
||||||
|
|
||||||
* Adds `all` pseudo-rule for `opt_in_rules` - enables all opt in rules
|
|
||||||
that are not listed in `disabled_rules`
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4540](https://github.com/realm/SwiftLint/issues/4540)
|
|
||||||
|
|
||||||
* Separate analyzer rules as an independent section in the rule directory of
|
|
||||||
the reference.
|
|
||||||
[Ethan Wong](https://github.com/GetToSet)
|
|
||||||
[#4664](https://github.com/realm/SwiftLint/pull/4664)
|
|
||||||
|
|
||||||
* Add rule identifier to output of Emoji reporter.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4707](https://github.com/realm/SwiftLint/issues/4707)
|
|
||||||
|
|
||||||
* Add new `direct_return` rule that triggers on `return` statements returning
|
|
||||||
variables that have been declared in the statement before only.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
|
|
||||||
* Add `period_spacing` opt-in rule that checks periods are not followed
|
|
||||||
by 2 or more spaces in comments.
|
|
||||||
[Julioacarrettoni](https://github.com/Julioacarrettoni)
|
|
||||||
[#4624](https://github.com/realm/SwiftLint/pull/4624)
|
|
||||||
|
|
||||||
* Allow to pass a rule identifier to the `swiftlint docs` command to open its
|
|
||||||
specific documentation website, e.g. `swiftlint docs for_where`.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4707](https://github.com/realm/SwiftLint/issues/4707)
|
|
||||||
|
|
||||||
* Allow new Quick APIs `aroundEach` and `justBeforeEach` for
|
|
||||||
`quick_discouraged_call`.
|
|
||||||
[David Steinacher](https://github.com/stonko1994)
|
|
||||||
[#4626](https://github.com/realm/SwiftLint/issues/4626)
|
|
||||||
|
|
||||||
* Add `relative-path` reporter to generate reports with relative file paths.
|
|
||||||
[Roya1v](https://github.com/roya1v)
|
|
||||||
[#4660](https://github.com/realm/SwiftLint/issues/4660)
|
|
||||||
|
|
||||||
* Let `number_separator` rule trigger on misplaced separators, e.g. `10_00`.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4637](https://github.com/realm/SwiftLint/issues/4637)
|
|
||||||
|
|
||||||
* Rewrite `multiline_arguments` rule using SwiftSyntax, ignoring trailing
|
|
||||||
closures.
|
|
||||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
|
||||||
[#3399](https://github.com/realm/SwiftLint/issues/3399)
|
|
||||||
[#3605](https://github.com/realm/SwiftLint/issues/3605)
|
|
||||||
|
|
||||||
* Speed up linting by up to 6% updating to use a newer version of
|
|
||||||
`SwiftSyntax`.
|
|
||||||
[JP Simard](https://github.com/jpsim)
|
|
||||||
|
|
||||||
* Catch more valid `legacy_multiple` violations.
|
|
||||||
[JP Simard](https://github.com/jpsim)
|
|
||||||
|
|
||||||
* Catch more valid `no_magic_numbers` violations.
|
|
||||||
[JP Simard](https://github.com/jpsim)
|
|
||||||
|
|
||||||
* Add `blanket_disable_command` rule that checks whether
|
|
||||||
rules are re-enabled after being disabled.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4731](https://github.com/realm/SwiftLint/pull/4731)
|
|
||||||
|
|
||||||
* Add `invalid_swiftlint_command` rule that validates
|
|
||||||
`// swiftlint:enable` and `disable` commands.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4546](https://github.com/realm/SwiftLint/pull/4546)
|
|
||||||
|
|
||||||
* Improve `identifier_name` documentation.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4767](https://github.com/realm/SwiftLint/issues/4767)
|
|
||||||
|
|
||||||
* Adds `include_multiline_strings` option to `indentation_width` rule.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4248](https://github.com/realm/SwiftLint/issues/4248)
|
|
||||||
|
|
||||||
* Adds a new `summary` reporter, that displays the number of violations
|
|
||||||
of each rule in a text table.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
|
|
||||||
#### Bug Fixes
|
|
||||||
|
|
||||||
* Report violations in all `<scope>_length` rules when the error threshold is
|
|
||||||
smaller than the warning threshold.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4645](https://github.com/realm/SwiftLint/issues/4645)
|
|
||||||
|
|
||||||
* Consider custom attributes in `attributes` rule.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4599](https://github.com/realm/SwiftLint/issues/4599)
|
|
||||||
|
|
||||||
* Fix whitespaces issue in auto-fix of `redundant_optional_initialization`
|
|
||||||
rule when multiple variable declaration are involved.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4794](https://github.com/realm/SwiftLint/issues/4794)
|
|
||||||
|
|
||||||
* Stop triggering `strict_fileprivate` rule on symbols implementing a protocol
|
|
||||||
in the same file.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4692](https://github.com/realm/SwiftLint/issues/4692)
|
|
||||||
|
|
||||||
* Fix false positives on `private_subject` rule when using
|
|
||||||
subjects inside functions.
|
|
||||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
|
||||||
[#4643](https://github.com/realm/SwiftLint/issues/4643)
|
|
||||||
|
|
||||||
* Fix for compiler directives masking subsequent `opening_brace`
|
|
||||||
violations.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#3712](https://github.com/realm/SwiftLint/issues/3712)
|
|
||||||
|
|
||||||
* Rewrite `explicit_type_interface` rule with SwiftSyntax fixing a
|
|
||||||
false-positive in if-case-let statements.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4548](https://github.com/realm/SwiftLint/issues/4548)
|
|
||||||
|
|
||||||
* Stop triggering `unused_capture_list` on captured variable that is only
|
|
||||||
referenced by a shorthand optional binding (`if let capturedVar { ... }`).
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4804](https://github.com/realm/SwiftLint/issues/4804)
|
|
||||||
|
|
||||||
* Ensure that negative literals in initializers do not trigger
|
|
||||||
`no_magic_numbers` rule.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4677](https://github.com/realm/SwiftLint/issues/4677)
|
|
||||||
|
|
||||||
* Fix caching of `indentation_width` rule.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4121](https://github.com/realm/SwiftLint/issues/4121)
|
|
||||||
|
|
||||||
* Updated JUnit reporter to output error count and warning count.
|
|
||||||
[patricks](https://github.com/patricks)
|
|
||||||
[#4725](https://github.com/realm/SwiftLint/pull/4725)
|
|
||||||
|
|
||||||
* Fix correction on `lower_acl_than_parent` rule for `open` declarations.
|
|
||||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
|
||||||
[#4753](https://github.com/realm/SwiftLint/issues/4753)
|
|
||||||
|
|
||||||
* Fix `void_return` rule to support async and async throws functions.
|
|
||||||
[Mathias Schreck](https://github.com/lo1tuma)
|
|
||||||
[#4772](https://github.com/realm/SwiftLint/issues/4772)
|
|
||||||
|
|
||||||
* Fix false positives in `attributes` rule when using property wrappers
|
|
||||||
with keypath arguments.
|
|
||||||
[JP Simard](https://github.com/jpsim)
|
|
||||||
|
|
||||||
* Fix for `superfluous_disable_command` not being completely disabled
|
|
||||||
by `disable` commands.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4788](https://github.com/realm/SwiftLint/issues/4788)
|
|
||||||
|
|
||||||
* Fixed correction for `trailing_comma` rule wrongly removing trailing
|
|
||||||
comments.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4814](https://github.com/realm/SwiftLint/issues/4814)
|
|
||||||
|
|
||||||
## 0.50.3: Bundle of Towels
|
|
||||||
|
|
||||||
#### Breaking
|
|
||||||
|
|
||||||
* None.
|
|
||||||
|
|
||||||
#### Experimental
|
|
||||||
|
|
||||||
* None.
|
|
||||||
|
|
||||||
#### Enhancements
|
|
||||||
|
|
||||||
* The `SwiftLintPlugin` SwiftPM plugin now uses a prebuilt binary on
|
|
||||||
macOS.
|
|
||||||
[Tony Arnold](https://github.com/tonyarnold)
|
|
||||||
[JP Simard](https://github.com/jpsim)
|
|
||||||
[#4558](https://github.com/realm/SwiftLint/issues/4558)
|
|
||||||
|
|
||||||
* Don't trigger `shorthand_operator` violations inside a shorthand
|
|
||||||
operator function declaration.
|
|
||||||
[Marcelo Fabri](https://github.com/marcelofabri)
|
|
||||||
[#4611](https://github.com/realm/SwiftLint/issues/4611)
|
|
||||||
|
|
||||||
* The `balanced_xctest_lifecycle`, `single_test_class`,
|
|
||||||
`empty_xctest_method` and `test_case_accessibility` rules will now be
|
|
||||||
applied to subclasses of `QuickSpec`, as well as `XCTestCase`, by
|
|
||||||
default.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
|
|
||||||
* Add `test_parent_classes` option to `balanced_xctest_lifecycle`,
|
|
||||||
`single_test_class` and `empty_xctest_method` rules.
|
|
||||||
[Martin Redington](https://github.com/mildm8nnered)
|
|
||||||
[#4200](https://github.com/realm/SwiftLint/issues/4200)
|
|
||||||
|
|
||||||
* Show warnings in the console for Analyzer rules that are listed in the
|
|
||||||
`opt_in_rules` configuration section.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4612](https://github.com/realm/SwiftLint/issues/4612)
|
|
||||||
|
|
||||||
#### Bug Fixes
|
|
||||||
|
|
||||||
* Fix configuration parsing error in `unused_declaration` rule.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4612](https://github.com/realm/SwiftLint/issues/4612)
|
|
||||||
|
|
||||||
* Skip `defer` statements being last in an `#if` block if the `#if`
|
|
||||||
statement is not itself the last statement in a block.
|
|
||||||
[SimplyDanny](https://github.com/SimplyDanny)
|
|
||||||
[#4615](https://github.com/realm/SwiftLint/issues/4615)
|
|
||||||
|
|
||||||
* Fix false positives in `empty_enum_arguments` when the called
|
|
||||||
expression is an identifier or an init call.
|
|
||||||
[Steffen Matthischke](https://github.com/heeaad)
|
|
||||||
[#4597](https://github.com/realm/SwiftLint/issues/4597)
|
|
||||||
|
|
||||||
* Fix correction issue in `comma` when there was too much whitespace
|
|
||||||
following the comma.
|
|
||||||
[JP Simard](https://github.com/jpsim)
|
|
||||||
|
|
||||||
## 0.50.1: Artisanal Clothes Pegs Fixup Edition
|
## 0.50.1: Artisanal Clothes Pegs Fixup Edition
|
||||||
|
|
||||||
#### Breaking
|
#### Breaking
|
||||||
|
|
|
@ -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
|
## Pull Requests
|
||||||
|
|
||||||
All changes, no matter how trivial, must be done via pull request. Commits
|
All changes, no matter how trivial, must be done via pull request. Commits
|
||||||
|
@ -64,7 +58,7 @@ $ make docker_test
|
||||||
|
|
||||||
## Rules
|
## 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.
|
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
|
* If none of the provided `RuleConfiguration`s are applicable, you can create one
|
||||||
specifically for your rule.
|
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,
|
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,
|
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:
|
for a rule that allows name evaluation configuration:
|
||||||
|
|
||||||
``` yaml
|
``` yaml
|
||||||
|
@ -180,5 +174,10 @@ To bring up a new Buildkite worker from MacStadium:
|
||||||
`brew install aria2 bazelisk htop buildkite/buildkite/buildkite-agent robotsandpencils/made/xcodes`
|
`brew install aria2 bazelisk htop buildkite/buildkite/buildkite-agent robotsandpencils/made/xcodes`
|
||||||
1. Install latest Xcode version: `xcodes update && xcodes install 14.0.0`
|
1. Install latest Xcode version: `xcodes update && xcodes install 14.0.0`
|
||||||
1. Add `DANGER_GITHUB_API_TOKEN` and `HOME` to `/opt/homebrew/etc/buildkite-agent/hooks/environment`
|
1. Add `DANGER_GITHUB_API_TOKEN` and `HOME` to `/opt/homebrew/etc/buildkite-agent/hooks/environment`
|
||||||
|
1. Add `echo "build --remote_cache=grpc://<creds>@swiftlint-ci.jpsim.com:9092" > ci.bazelrc`
|
||||||
|
to `/opt/homebrew/etc/buildkite-agent/hooks/pre-command`, replacing `<creds>` with the
|
||||||
|
bazel-remote credentials
|
||||||
1. Configure and launch buildkite agent: `brew info buildkite-agent` /
|
1. Configure and launch buildkite agent: `brew info buildkite-agent` /
|
||||||
https://buildkite.com/organizations/swiftlint/agents#setup-macos
|
https://buildkite.com/organizations/swiftlint/agents#setup-macos
|
||||||
|
1. On the `swiftlint-ci.jpsim.com` machine only:
|
||||||
|
`docker run -d -u 1000:1000 -v /tmp/swiftlint-bazel-remote-cache:/data -v ~/Desktop/bazel-remote.htpasswd:/etc/bazel-remote/htpasswd -p 9090:8080 -p 9092:9092 buchgr/bazel-remote-cache --htpasswd_file=/etc/bazel-remote/htpasswd --max_size=5`
|
||||||
|
|
|
@ -16,7 +16,7 @@ has_app_changes = !modified_files.grep(/Source/).empty?
|
||||||
has_test_changes = !modified_files.grep(/Tests/).empty?
|
has_test_changes = !modified_files.grep(/Tests/).empty?
|
||||||
has_danger_changes = !modified_files.grep(/Dangerfile|tools\/oss-check|Gemfile/).empty?
|
has_danger_changes = !modified_files.grep(/Dangerfile|tools\/oss-check|Gemfile/).empty?
|
||||||
has_package_changes = !modified_files.grep(/Package\.swift/).empty?
|
has_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
|
# Add a CHANGELOG entry for app changes
|
||||||
if !modified_files.include?('CHANGELOG.md') && has_app_changes
|
if !modified_files.include?('CHANGELOG.md') && has_app_changes
|
||||||
|
|
80
Gemfile.lock
80
Gemfile.lock
|
@ -1,14 +1,15 @@
|
||||||
GEM
|
GEM
|
||||||
remote: https://rubygems.org/
|
remote: https://rubygems.org/
|
||||||
specs:
|
specs:
|
||||||
CFPropertyList (3.0.6)
|
CFPropertyList (3.0.5)
|
||||||
rexml
|
rexml
|
||||||
activesupport (7.0.4.3)
|
activesupport (6.1.6.1)
|
||||||
concurrent-ruby (~> 1.0, >= 1.0.2)
|
concurrent-ruby (~> 1.0, >= 1.0.2)
|
||||||
i18n (>= 1.6, < 2)
|
i18n (>= 1.6, < 2)
|
||||||
minitest (>= 5.1)
|
minitest (>= 5.1)
|
||||||
tzinfo (~> 2.0)
|
tzinfo (~> 2.0)
|
||||||
addressable (2.8.4)
|
zeitwerk (~> 2.3)
|
||||||
|
addressable (2.8.1)
|
||||||
public_suffix (>= 2.0.2, < 6.0)
|
public_suffix (>= 2.0.2, < 6.0)
|
||||||
algoliasearch (1.27.5)
|
algoliasearch (1.27.5)
|
||||||
httpclient (~> 2.8, >= 2.8.3)
|
httpclient (~> 2.8, >= 2.8.3)
|
||||||
|
@ -19,15 +20,15 @@ GEM
|
||||||
cork
|
cork
|
||||||
nap
|
nap
|
||||||
open4 (~> 1.3)
|
open4 (~> 1.3)
|
||||||
cocoapods (1.12.1)
|
cocoapods (1.11.3)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
claide (>= 1.0.2, < 2.0)
|
claide (>= 1.0.2, < 2.0)
|
||||||
cocoapods-core (= 1.12.1)
|
cocoapods-core (= 1.11.3)
|
||||||
cocoapods-deintegrate (>= 1.0.3, < 2.0)
|
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-plugins (>= 1.0.0, < 2.0)
|
||||||
cocoapods-search (>= 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)
|
cocoapods-try (>= 1.1.0, < 2.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
escape (~> 0.0.4)
|
escape (~> 0.0.4)
|
||||||
|
@ -35,10 +36,10 @@ GEM
|
||||||
gh_inspector (~> 1.0)
|
gh_inspector (~> 1.0)
|
||||||
molinillo (~> 0.8.0)
|
molinillo (~> 0.8.0)
|
||||||
nap (~> 1.0)
|
nap (~> 1.0)
|
||||||
ruby-macho (>= 2.3.0, < 3.0)
|
ruby-macho (>= 1.0, < 3.0)
|
||||||
xcodeproj (>= 1.21.0, < 2.0)
|
xcodeproj (>= 1.21.0, < 2.0)
|
||||||
cocoapods-core (1.12.1)
|
cocoapods-core (1.11.3)
|
||||||
activesupport (>= 5.0, < 8)
|
activesupport (>= 5.0, < 7)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
algoliasearch (~> 1.0)
|
algoliasearch (~> 1.0)
|
||||||
concurrent-ruby (~> 1.1)
|
concurrent-ruby (~> 1.1)
|
||||||
|
@ -57,42 +58,61 @@ GEM
|
||||||
netrc (~> 0.11)
|
netrc (~> 0.11)
|
||||||
cocoapods-try (1.2.0)
|
cocoapods-try (1.2.0)
|
||||||
colored2 (3.1.2)
|
colored2 (3.1.2)
|
||||||
concurrent-ruby (1.2.2)
|
concurrent-ruby (1.1.10)
|
||||||
cork (0.3.0)
|
cork (0.3.0)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
danger (9.2.0)
|
danger (8.6.1)
|
||||||
claide (~> 1.0)
|
claide (~> 1.0)
|
||||||
claide-plugins (>= 0.9.2)
|
claide-plugins (>= 0.9.2)
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
cork (~> 0.1)
|
cork (~> 0.1)
|
||||||
faraday (>= 0.9.0, < 3.0)
|
faraday (>= 0.9.0, < 2.0)
|
||||||
faraday-http-cache (~> 2.0)
|
faraday-http-cache (~> 2.0)
|
||||||
git (~> 1.7)
|
git (~> 1.7)
|
||||||
kramdown (~> 2.3)
|
kramdown (~> 2.3)
|
||||||
kramdown-parser-gfm (~> 1.0)
|
kramdown-parser-gfm (~> 1.0)
|
||||||
no_proxy_fix
|
no_proxy_fix
|
||||||
octokit (~> 5.0)
|
octokit (~> 4.7)
|
||||||
terminal-table (>= 1, < 4)
|
terminal-table (>= 1, < 4)
|
||||||
escape (0.0.4)
|
escape (0.0.4)
|
||||||
ethon (0.16.0)
|
ethon (0.15.0)
|
||||||
ffi (>= 1.15.0)
|
ffi (>= 1.15.0)
|
||||||
faraday (2.7.4)
|
faraday (1.10.2)
|
||||||
faraday-net_http (>= 2.0, < 3.1)
|
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)
|
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-http-cache (2.4.1)
|
||||||
faraday (>= 0.8)
|
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)
|
ffi (1.15.5)
|
||||||
fourflusher (2.3.1)
|
fourflusher (2.3.1)
|
||||||
fuzzy_match (2.0.4)
|
fuzzy_match (2.0.4)
|
||||||
gh_inspector (1.1.3)
|
gh_inspector (1.1.3)
|
||||||
git (1.18.0)
|
git (1.12.0)
|
||||||
addressable (~> 2.8)
|
addressable (~> 2.8)
|
||||||
rchardet (~> 1.8)
|
rchardet (~> 1.8)
|
||||||
httpclient (2.8.3)
|
httpclient (2.8.3)
|
||||||
i18n (1.12.0)
|
i18n (1.12.0)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
jazzy (0.14.3)
|
jazzy (0.14.2)
|
||||||
cocoapods (~> 1.5)
|
cocoapods (~> 1.5)
|
||||||
mustache (~> 1.1)
|
mustache (~> 1.1)
|
||||||
open4 (~> 1.3)
|
open4 (~> 1.3)
|
||||||
|
@ -102,26 +122,27 @@ GEM
|
||||||
sassc (~> 2.1)
|
sassc (~> 2.1)
|
||||||
sqlite3 (~> 1.3)
|
sqlite3 (~> 1.3)
|
||||||
xcinvoke (~> 0.3.0)
|
xcinvoke (~> 0.3.0)
|
||||||
json (2.6.3)
|
json (2.6.2)
|
||||||
kramdown (2.4.0)
|
kramdown (2.4.0)
|
||||||
rexml
|
rexml
|
||||||
kramdown-parser-gfm (1.1.0)
|
kramdown-parser-gfm (1.1.0)
|
||||||
kramdown (~> 2.0)
|
kramdown (~> 2.0)
|
||||||
liferaft (0.0.6)
|
liferaft (0.0.6)
|
||||||
minitest (5.18.0)
|
minitest (5.16.3)
|
||||||
molinillo (0.8.0)
|
molinillo (0.8.0)
|
||||||
|
multipart-post (2.2.3)
|
||||||
mustache (1.1.1)
|
mustache (1.1.1)
|
||||||
nanaimo (0.3.0)
|
nanaimo (0.3.0)
|
||||||
nap (1.1.0)
|
nap (1.1.0)
|
||||||
netrc (0.11.0)
|
netrc (0.11.0)
|
||||||
no_proxy_fix (0.1.2)
|
no_proxy_fix (0.1.2)
|
||||||
octokit (5.6.1)
|
octokit (4.25.1)
|
||||||
faraday (>= 1, < 3)
|
faraday (>= 1, < 3)
|
||||||
sawyer (~> 0.9)
|
sawyer (~> 0.9)
|
||||||
open4 (1.3.4)
|
open4 (1.3.4)
|
||||||
public_suffix (4.0.7)
|
public_suffix (4.0.7)
|
||||||
rchardet (1.8.0)
|
rchardet (1.8.0)
|
||||||
redcarpet (3.6.0)
|
redcarpet (3.5.1)
|
||||||
rexml (3.2.5)
|
rexml (3.2.5)
|
||||||
rouge (3.30.0)
|
rouge (3.30.0)
|
||||||
ruby-macho (2.5.1)
|
ruby-macho (2.5.1)
|
||||||
|
@ -131,14 +152,14 @@ GEM
|
||||||
sawyer (0.9.2)
|
sawyer (0.9.2)
|
||||||
addressable (>= 2.3.5)
|
addressable (>= 2.3.5)
|
||||||
faraday (>= 0.17.3, < 3)
|
faraday (>= 0.17.3, < 3)
|
||||||
sqlite3 (1.6.2-arm64-darwin)
|
sqlite3 (1.4.4)
|
||||||
terminal-table (3.0.2)
|
terminal-table (3.0.2)
|
||||||
unicode-display_width (>= 1.1.1, < 3)
|
unicode-display_width (>= 1.1.1, < 3)
|
||||||
typhoeus (1.4.0)
|
typhoeus (1.4.0)
|
||||||
ethon (>= 0.9.0)
|
ethon (>= 0.9.0)
|
||||||
tzinfo (2.0.6)
|
tzinfo (2.0.5)
|
||||||
concurrent-ruby (~> 1.0)
|
concurrent-ruby (~> 1.0)
|
||||||
unicode-display_width (2.4.2)
|
unicode-display_width (2.2.0)
|
||||||
xcinvoke (0.3.0)
|
xcinvoke (0.3.0)
|
||||||
liferaft (~> 0.0.6)
|
liferaft (~> 0.0.6)
|
||||||
xcodeproj (1.22.0)
|
xcodeproj (1.22.0)
|
||||||
|
@ -148,10 +169,11 @@ GEM
|
||||||
colored2 (~> 3.1)
|
colored2 (~> 3.1)
|
||||||
nanaimo (~> 0.3.0)
|
nanaimo (~> 0.3.0)
|
||||||
rexml (~> 3.2.4)
|
rexml (~> 3.2.4)
|
||||||
|
zeitwerk (2.6.0)
|
||||||
|
|
||||||
PLATFORMS
|
PLATFORMS
|
||||||
arm64-darwin-21
|
arm64-darwin-21
|
||||||
arm64-darwin-22
|
ruby
|
||||||
|
|
||||||
DEPENDENCIES
|
DEPENDENCIES
|
||||||
cocoapods
|
cocoapods
|
||||||
|
@ -159,4 +181,4 @@ DEPENDENCIES
|
||||||
jazzy
|
jazzy
|
||||||
|
|
||||||
BUNDLED WITH
|
BUNDLED WITH
|
||||||
2.4.12
|
2.3.8
|
||||||
|
|
27
MODULE.bazel
27
MODULE.bazel
|
@ -1,27 +0,0 @@
|
||||||
module(
|
|
||||||
name = "swiftlint",
|
|
||||||
version = "0.52.2",
|
|
||||||
compatibility_level = 1,
|
|
||||||
repo_name = "SwiftLint",
|
|
||||||
)
|
|
||||||
|
|
||||||
bazel_dep(name = "bazel_skylib", version = "1.4.1", dev_dependency = True)
|
|
||||||
bazel_dep(name = "platforms", version = "0.0.6")
|
|
||||||
bazel_dep(name = "rules_apple", version = "2.3.0", repo_name = "build_bazel_rules_apple")
|
|
||||||
bazel_dep(name = "rules_swift", version = "1.8.0", repo_name = "build_bazel_rules_swift")
|
|
||||||
bazel_dep(name = "rules_xcodeproj", version = "1.7.0")
|
|
||||||
bazel_dep(name = "sourcekitten", version = "0.34.1", repo_name = "com_github_jpsim_sourcekitten")
|
|
||||||
bazel_dep(name = "swift_argument_parser", version = "1.2.1", repo_name = "sourcekitten_com_github_apple_swift_argument_parser")
|
|
||||||
bazel_dep(name = "yams", version = "5.0.5", repo_name = "sourcekitten_com_github_jpsim_yams")
|
|
||||||
|
|
||||||
swiftlint_repos = use_extension("//bazel:repos.bzl", "swiftlint_repos_bzlmod")
|
|
||||||
use_repo(
|
|
||||||
swiftlint_repos,
|
|
||||||
"SwiftSyntax",
|
|
||||||
"com_github_johnsundell_collectionconcurrencykit",
|
|
||||||
"com_github_krzyzanowskim_cryptoswift",
|
|
||||||
"swiftlint_com_github_scottrhoyt_swifty_text_table",
|
|
||||||
)
|
|
||||||
|
|
||||||
extra_rules = use_extension("//bazel:extensions.bzl", "extra_rules")
|
|
||||||
use_repo(extra_rules, "swiftlint_extra_rules")
|
|
45
Makefile
45
Makefile
|
@ -30,18 +30,14 @@ VERSION_STRING=$(shell ./tools/get-version)
|
||||||
|
|
||||||
all: build
|
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
|
Source/SwiftLintFramework/Models/PrimaryRuleList.swift: Source/SwiftLintFramework/Rules/**/*.swift .sourcery/PrimaryRuleList.stencil
|
||||||
./tools/sourcery --sources Source/SwiftLintBuiltInRules/Rules --templates .sourcery/BuiltInRules.stencil --output .sourcery
|
sourcery --sources Source/SwiftLintFramework/Rules --templates .sourcery/PrimaryRuleList.stencil --output .sourcery
|
||||||
mv .sourcery/BuiltInRules.generated.swift Source/SwiftLintBuiltInRules/Models/BuiltInRules.swift
|
mv .sourcery/PrimaryRuleList.generated.swift Source/SwiftLintFramework/Models/PrimaryRuleList.swift
|
||||||
|
|
||||||
Source/SwiftLintCore/Models/ReportersList.swift: Source/SwiftLintCore/Reporters/*.swift .sourcery/ReportersList.stencil
|
Tests/GeneratedTests/GeneratedTests.swift: Source/SwiftLintFramework/Rules/**/*.swift .sourcery/GeneratedTests.stencil
|
||||||
./tools/sourcery --sources Source/SwiftLintCore/Reporters --templates .sourcery/ReportersList.stencil --output .sourcery
|
sourcery --sources Source/SwiftLintFramework/Rules --templates .sourcery/GeneratedTests.stencil --output .sourcery
|
||||||
mv .sourcery/ReportersList.generated.swift Source/SwiftLintCore/Models/ReportersList.swift
|
|
||||||
|
|
||||||
Tests/GeneratedTests/GeneratedTests.swift: Source/SwiftLint*/Rules/**/*.swift .sourcery/GeneratedTests.stencil
|
|
||||||
./tools/sourcery --sources Source/SwiftLintCore/Rules --sources Source/SwiftLintBuiltInRules/Rules --templates .sourcery/GeneratedTests.stencil --output .sourcery
|
|
||||||
mv .sourcery/GeneratedTests.generated.swift Tests/GeneratedTests/GeneratedTests.swift
|
mv .sourcery/GeneratedTests.generated.swift Tests/GeneratedTests/GeneratedTests.swift
|
||||||
|
|
||||||
test: clean_xcode
|
test: clean_xcode
|
||||||
|
@ -69,7 +65,7 @@ clean:
|
||||||
clean_xcode:
|
clean_xcode:
|
||||||
$(BUILD_TOOL) $(XCODEFLAGS) -configuration Test clean
|
$(BUILD_TOOL) $(XCODEFLAGS) -configuration Test clean
|
||||||
|
|
||||||
build:
|
build: clean
|
||||||
mkdir -p "$(SWIFTLINT_EXECUTABLE_PARENT)"
|
mkdir -p "$(SWIFTLINT_EXECUTABLE_PARENT)"
|
||||||
bazel build --config release universal_swiftlint
|
bazel build --config release universal_swiftlint
|
||||||
$(eval SWIFTLINT_BINARY := $(shell bazel cquery --config release --output=files universal_swiftlint))
|
$(eval SWIFTLINT_BINARY := $(shell bazel cquery --config release --output=files universal_swiftlint))
|
||||||
|
@ -117,11 +113,10 @@ zip_linux: docker_image
|
||||||
|
|
||||||
zip_linux_release:
|
zip_linux_release:
|
||||||
$(eval TMP_FOLDER := $(shell mktemp -d))
|
$(eval TMP_FOLDER := $(shell mktemp -d))
|
||||||
docker run --platform linux/amd64 "ghcr.io/realm/swiftlint:$(VERSION_STRING)" cat /usr/bin/swiftlint > "$(TMP_FOLDER)/swiftlint"
|
docker run "ghcr.io/realm/swiftlint:$(VERSION_STRING)" cat /usr/bin/swiftlint > "$(TMP_FOLDER)/swiftlint"
|
||||||
chmod +x "$(TMP_FOLDER)/swiftlint"
|
chmod +x "$(TMP_FOLDER)/swiftlint"
|
||||||
cp -f "$(LICENSE_PATH)" "$(TMP_FOLDER)"
|
cp -f "$(LICENSE_PATH)" "$(TMP_FOLDER)"
|
||||||
(cd "$(TMP_FOLDER)"; zip -yr - "swiftlint" "LICENSE") > "./swiftlint_linux.zip"
|
(cd "$(TMP_FOLDER)"; zip -yr - "swiftlint" "LICENSE") > "./swiftlint_linux.zip"
|
||||||
gh release upload "$(VERSION_STRING)" swiftlint_linux.zip
|
|
||||||
|
|
||||||
package: build
|
package: build
|
||||||
$(eval PACKAGE_ROOT := $(shell mktemp -d))
|
$(eval PACKAGE_ROOT := $(shell mktemp -d))
|
||||||
|
@ -137,11 +132,13 @@ bazel_release:
|
||||||
bazel build :release
|
bazel build :release
|
||||||
mv bazel-bin/bazel.tar.gz bazel-bin/bazel.tar.gz.sha256 .
|
mv bazel-bin/bazel.tar.gz bazel-bin/bazel.tar.gz.sha256 .
|
||||||
|
|
||||||
|
release: clean bazel_release package portable_zip spm_artifactbundle_macos zip_linux_release
|
||||||
|
|
||||||
docker_image:
|
docker_image:
|
||||||
docker build --platform linux/amd64 --force-rm --tag swiftlint .
|
docker build --platform linux/amd64 --force-rm --tag swiftlint .
|
||||||
|
|
||||||
docker_test:
|
docker_test:
|
||||||
docker run -v `pwd`:`pwd` -w `pwd` --name swiftlint --rm swift:5.7-focal swift test --parallel
|
docker run -v `pwd`:`pwd` -w `pwd` --name swiftlint --rm swift:5.6-focal swift test --parallel
|
||||||
|
|
||||||
docker_htop:
|
docker_htop:
|
||||||
docker run --platform linux/amd64 -it --rm --pid=container:swiftlint terencewestphal/htop || reset
|
docker run --platform linux/amd64 -it --rm --pid=container:swiftlint terencewestphal/htop || reset
|
||||||
|
@ -152,8 +149,8 @@ display_compilation_time:
|
||||||
|
|
||||||
publish:
|
publish:
|
||||||
brew update && brew bump-formula-pr --tag=$(shell git describe --tags) --revision=$(shell git rev-parse HEAD) swiftlint
|
brew update && brew bump-formula-pr --tag=$(shell git describe --tags) --revision=$(shell git rev-parse HEAD) swiftlint
|
||||||
bundle install
|
# Workaround for https://github.com/CocoaPods/CocoaPods/issues/11185
|
||||||
bundle exec pod trunk push SwiftLint.podspec
|
arch -arch x86_64 pod trunk push SwiftLint.podspec
|
||||||
|
|
||||||
docs:
|
docs:
|
||||||
swift run swiftlint generate-docs
|
swift run swiftlint generate-docs
|
||||||
|
@ -163,30 +160,18 @@ docs:
|
||||||
get_version:
|
get_version:
|
||||||
@echo "$(VERSION_STRING)"
|
@echo "$(VERSION_STRING)"
|
||||||
|
|
||||||
release:
|
push_version:
|
||||||
ifneq ($(strip $(shell git status --untracked-files=no --porcelain 2>/dev/null)),)
|
ifneq ($(strip $(shell git status --untracked-files=no --porcelain 2>/dev/null)),)
|
||||||
$(error git state is not clean)
|
$(error git state is not clean)
|
||||||
endif
|
endif
|
||||||
$(eval NEW_VERSION_AND_NAME := $(filter-out $@,$(MAKECMDGOALS)))
|
$(eval NEW_VERSION_AND_NAME := $(filter-out $@,$(MAKECMDGOALS)))
|
||||||
$(eval NEW_VERSION := $(shell echo $(NEW_VERSION_AND_NAME) | sed 's/:.*//' ))
|
$(eval NEW_VERSION := $(shell echo $(NEW_VERSION_AND_NAME) | sed 's/:.*//' ))
|
||||||
@sed -i '' 's/## Main/## $(NEW_VERSION_AND_NAME)/g' CHANGELOG.md
|
@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
|
|
||||||
make spm_artifactbundle_macos
|
|
||||||
./tools/update-artifact-bundle.sh "$(NEW_VERSION)"
|
|
||||||
git commit -a -m "release $(NEW_VERSION)"
|
git commit -a -m "release $(NEW_VERSION)"
|
||||||
git tag -a $(NEW_VERSION) -m "$(NEW_VERSION_AND_NAME)"
|
git tag -a $(NEW_VERSION) -m "$(NEW_VERSION_AND_NAME)"
|
||||||
git push origin HEAD
|
git push origin HEAD
|
||||||
git push origin $(NEW_VERSION)
|
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
|
|
||||||
|
|
||||||
%:
|
%:
|
||||||
@:
|
@:
|
||||||
|
|
|
@ -9,22 +9,13 @@
|
||||||
"version" : "0.2.0"
|
"version" : "0.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
|
||||||
"identity" : "cryptoswift",
|
|
||||||
"kind" : "remoteSourceControl",
|
|
||||||
"location" : "https://github.com/krzyzanowskim/CryptoSwift.git",
|
|
||||||
"state" : {
|
|
||||||
"revision" : "32f641cf24fc7abc1c591a2025e9f2f572648b0f",
|
|
||||||
"version" : "1.7.2"
|
|
||||||
}
|
|
||||||
},
|
|
||||||
{
|
{
|
||||||
"identity" : "sourcekitten",
|
"identity" : "sourcekitten",
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/jpsim/SourceKitten.git",
|
"location" : "https://github.com/jpsim/SourceKitten.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "b6dc09ee51dfb0c66e042d2328c017483a1a5d56",
|
"revision" : "fc12c0f182c5cf80781dd933b17a82eb98bd7c61",
|
||||||
"version" : "0.34.1"
|
"version" : "0.33.1"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -32,8 +23,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-argument-parser.git",
|
"location" : "https://github.com/apple/swift-argument-parser.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "4ad606ba5d7673ea60679a61ff867cc1ff8c8e86",
|
"revision" : "fddd1c00396eed152c45a46bea9f47b98e59301d",
|
||||||
"version" : "1.2.1"
|
"version" : "1.2.0"
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -41,8 +32,7 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/apple/swift-syntax.git",
|
"location" : "https://github.com/apple/swift-syntax.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "165fc6d22394c1168ff76ab5d951245971ef07e5",
|
"revision" : "a82041008d2c678a97407fbd0ce420d3ab047538"
|
||||||
"version" : "509.0.0-swift-DEVELOPMENT-SNAPSHOT-2023-06-05-a"
|
|
||||||
}
|
}
|
||||||
},
|
},
|
||||||
{
|
{
|
||||||
|
@ -68,8 +58,8 @@
|
||||||
"kind" : "remoteSourceControl",
|
"kind" : "remoteSourceControl",
|
||||||
"location" : "https://github.com/jpsim/Yams.git",
|
"location" : "https://github.com/jpsim/Yams.git",
|
||||||
"state" : {
|
"state" : {
|
||||||
"revision" : "f47ba4838c30dbd59998a4e4c87ab620ff959e8a",
|
"revision" : "01835dc202670b5bb90d07f3eae41867e9ed29f6",
|
||||||
"version" : "5.0.5"
|
"version" : "5.0.1"
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
],
|
],
|
||||||
|
|
|
@ -1,6 +1,23 @@
|
||||||
// swift-tools-version:5.7
|
// swift-tools-version:5.7
|
||||||
import PackageDescription
|
import PackageDescription
|
||||||
|
|
||||||
|
#if os(macOS)
|
||||||
|
private let addCryptoSwift = false
|
||||||
|
#else
|
||||||
|
private let addCryptoSwift = true
|
||||||
|
#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(
|
let package = Package(
|
||||||
name: "SwiftLint",
|
name: "SwiftLint",
|
||||||
platforms: [.macOS(.v12)],
|
platforms: [.macOS(.v12)],
|
||||||
|
@ -10,21 +27,19 @@ let package = Package(
|
||||||
.plugin(name: "SwiftLintPlugin", targets: ["SwiftLintPlugin"])
|
.plugin(name: "SwiftLintPlugin", targets: ["SwiftLintPlugin"])
|
||||||
],
|
],
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.1")),
|
.package(url: "https://github.com/apple/swift-argument-parser.git", .upToNextMinor(from: "1.2.0")),
|
||||||
.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", revision: "a82041008d2c678a97407fbd0ce420d3ab047538"),
|
||||||
.package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.34.1")),
|
.package(url: "https://github.com/jpsim/SourceKitten.git", .upToNextMinor(from: "0.33.1")),
|
||||||
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.5"),
|
.package(url: "https://github.com/jpsim/Yams.git", from: "5.0.1"),
|
||||||
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
|
.package(url: "https://github.com/scottrhoyt/SwiftyTextTable.git", from: "0.9.0"),
|
||||||
.package(url: "https://github.com/JohnSundell/CollectionConcurrencyKit.git", from: "0.2.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"))
|
] + (addCryptoSwift ? [.package(url: "https://github.com/krzyzanowskim/CryptoSwift.git", .upToNextMinor(from: "1.6.0"))] : []),
|
||||||
],
|
|
||||||
targets: [
|
targets: [
|
||||||
.plugin(
|
.plugin(
|
||||||
name: "SwiftLintPlugin",
|
name: "SwiftLintPlugin",
|
||||||
capability: .buildTool(),
|
capability: .buildTool(),
|
||||||
dependencies: [
|
dependencies: [
|
||||||
.target(name: "SwiftLintBinary", condition: .when(platforms: [.macOS])),
|
.target(name: "swiftlint")
|
||||||
.target(name: "swiftlint", condition: .when(platforms: [.linux]))
|
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.executableTarget(
|
.executableTarget(
|
||||||
|
@ -42,44 +57,15 @@ let package = Package(
|
||||||
"swiftlint"
|
"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(
|
.target(
|
||||||
name: "SwiftLintFramework",
|
name: "SwiftLintFramework",
|
||||||
dependencies: [
|
dependencies: frameworkDependencies
|
||||||
"SwiftLintBuiltInRules",
|
|
||||||
"SwiftLintCore",
|
|
||||||
"SwiftLintExtraRules"
|
|
||||||
]
|
|
||||||
),
|
),
|
||||||
.target(name: "DyldWarningWorkaround"),
|
.testTarget(
|
||||||
.target(
|
|
||||||
name: "SwiftLintTestHelpers",
|
name: "SwiftLintTestHelpers",
|
||||||
dependencies: [
|
dependencies: [
|
||||||
"SwiftLintFramework"
|
"SwiftLintFramework"
|
||||||
],
|
]
|
||||||
path: "Tests/SwiftLintTestHelpers"
|
|
||||||
),
|
),
|
||||||
.testTarget(
|
.testTarget(
|
||||||
name: "SwiftLintFrameworkTests",
|
name: "SwiftLintFrameworkTests",
|
||||||
|
@ -112,10 +98,5 @@ let package = Package(
|
||||||
"SwiftLintTestHelpers"
|
"SwiftLintTestHelpers"
|
||||||
]
|
]
|
||||||
),
|
),
|
||||||
.binaryTarget(
|
|
||||||
name: "SwiftLintBinary",
|
|
||||||
url: "https://github.com/realm/SwiftLint/releases/download/0.52.2/SwiftLintBinary-macos.artifactbundle.zip",
|
|
||||||
checksum: "89651e1c87fb62faf076ef785a5b1af7f43570b2b74c6773526e0d5114e0578e"
|
|
||||||
)
|
|
||||||
]
|
]
|
||||||
)
|
)
|
||||||
|
|
|
@ -1,42 +1,16 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
import PackagePlugin
|
import PackagePlugin
|
||||||
|
|
||||||
#if os(Linux)
|
|
||||||
import Glibc
|
|
||||||
#else
|
|
||||||
import Darwin
|
|
||||||
#endif
|
|
||||||
|
|
||||||
extension Path {
|
extension Path {
|
||||||
/// Scans the receiver, then all of its parents looking for a configuration file with the name ".swiftlint.yml".
|
/// Scans the receiver, then all of its parents looking for a configuration file with the name ".swiftlint.yml".
|
||||||
///
|
///
|
||||||
/// - returns: Path to the configuration file, or nil if one cannot be found.
|
/// - returns: Path to the configuration file, or nil if one cannot be found.
|
||||||
func firstConfigurationFileInParentDirectories() -> Path? {
|
func firstConfigurationFileInParentDirectories() -> Path? {
|
||||||
let defaultConfigurationFileName = ".swiftlint.yml"
|
let defaultConfigurationFileName = ".swiftlint.yml"
|
||||||
let proposedDirectory = sequence(
|
let proposedDirectory = sequence(first: self, next: { $0.removingLastComponent() }).first { path in
|
||||||
first: self,
|
|
||||||
next: { path in
|
|
||||||
guard path.stem.count > 1 else {
|
|
||||||
// Check we're not at the root of this filesystem, as `removingLastComponent()`
|
|
||||||
// will continually return the root from itself.
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
|
|
||||||
return path.removingLastComponent()
|
|
||||||
}
|
|
||||||
).first { path in
|
|
||||||
let potentialConfigurationFile = path.appending(subpath: defaultConfigurationFileName)
|
let potentialConfigurationFile = path.appending(subpath: defaultConfigurationFileName)
|
||||||
return potentialConfigurationFile.isAccessible()
|
return FileManager.default.isReadableFile(atPath: potentialConfigurationFile.string)
|
||||||
}
|
}
|
||||||
return proposedDirectory?.appending(subpath: defaultConfigurationFileName)
|
return proposedDirectory?.appending(subpath: defaultConfigurationFileName)
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Safe way to check if the file is accessible from within the current process sandbox.
|
|
||||||
private func isAccessible() -> Bool {
|
|
||||||
let result = string.withCString { pointer in
|
|
||||||
access(pointer, R_OK)
|
|
||||||
}
|
|
||||||
|
|
||||||
return result == 0
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
|
@ -3,55 +3,45 @@ import PackagePlugin
|
||||||
|
|
||||||
@main
|
@main
|
||||||
struct SwiftLintPlugin: BuildToolPlugin {
|
struct SwiftLintPlugin: BuildToolPlugin {
|
||||||
func createBuildCommands(context: PluginContext, target: Target) async throws -> [Command] {
|
func createBuildCommands(
|
||||||
|
context: PackagePlugin.PluginContext,
|
||||||
|
target: PackagePlugin.Target
|
||||||
|
) async throws -> [PackagePlugin.Command] {
|
||||||
guard let sourceTarget = target as? SourceModuleTarget else {
|
guard let sourceTarget = target as? SourceModuleTarget else {
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
return createBuildCommands(
|
|
||||||
inputFiles: sourceTarget.sourceFiles(withSuffix: "swift").map(\.path),
|
|
||||||
packageDirectory: context.package.directory,
|
|
||||||
workingDirectory: context.pluginWorkDirectory,
|
|
||||||
tool: try context.tool(named: "swiftlint")
|
|
||||||
)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func createBuildCommands(
|
let inputFilePaths = sourceTarget.sourceFiles(withSuffix: "swift")
|
||||||
inputFiles: [Path],
|
.map(\.path)
|
||||||
packageDirectory: Path,
|
|
||||||
workingDirectory: Path,
|
guard inputFilePaths.isEmpty == false else {
|
||||||
tool: PluginContext.Tool
|
|
||||||
) -> [Command] {
|
|
||||||
if inputFiles.isEmpty {
|
|
||||||
// Don't lint anything if there are no Swift source files in this target
|
// Don't lint anything if there are no Swift source files in this target
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
var arguments = [
|
let swiftlint = try context.tool(named: "swiftlint")
|
||||||
|
var arguments: [String] = [
|
||||||
"lint",
|
"lint",
|
||||||
"--quiet",
|
"--cache-path", "\(context.pluginWorkDirectory)"
|
||||||
// We always pass all of the Swift source files in the target to the tool,
|
|
||||||
// so we need to ensure that any exclusion rules in the configuration are
|
|
||||||
// respected.
|
|
||||||
"--force-exclude",
|
|
||||||
"--cache-path", "\(workingDirectory)"
|
|
||||||
]
|
]
|
||||||
|
|
||||||
// Manually look for configuration files, to avoid issues when the plugin does not execute our tool from the
|
// Manually look for configuration files, to avoid issues when the plugin does not execute our tool from the
|
||||||
// package source directory.
|
// package source directory.
|
||||||
if let configuration = packageDirectory.firstConfigurationFileInParentDirectories() {
|
if let configuration = context.package.directory.firstConfigurationFileInParentDirectories() {
|
||||||
arguments.append(contentsOf: ["--config", "\(configuration.string)"])
|
arguments.append(contentsOf: [
|
||||||
|
"--config", "\(configuration.string)"
|
||||||
|
])
|
||||||
}
|
}
|
||||||
arguments += inputFiles.map(\.string)
|
|
||||||
|
|
||||||
// We are not producing output files and this is needed only to not include cache files into bundle
|
arguments += inputFilePaths.map(\.string)
|
||||||
let outputFilesDirectory = workingDirectory.appending("Output")
|
|
||||||
|
|
||||||
return [
|
return [
|
||||||
.prebuildCommand(
|
.buildCommand(
|
||||||
displayName: "SwiftLint",
|
displayName: "SwiftLint",
|
||||||
executable: tool.path,
|
executable: swiftlint.path,
|
||||||
arguments: arguments,
|
arguments: arguments,
|
||||||
outputFilesDirectory: outputFilesDirectory
|
inputFiles: inputFilePaths,
|
||||||
|
outputFiles: [context.pluginWorkDirectory]
|
||||||
)
|
)
|
||||||
]
|
]
|
||||||
}
|
}
|
||||||
|
@ -61,16 +51,44 @@ struct SwiftLintPlugin: BuildToolPlugin {
|
||||||
import XcodeProjectPlugin
|
import XcodeProjectPlugin
|
||||||
|
|
||||||
extension SwiftLintPlugin: XcodeBuildToolPlugin {
|
extension SwiftLintPlugin: XcodeBuildToolPlugin {
|
||||||
func createBuildCommands(context: XcodePluginContext, target: XcodeTarget) throws -> [Command] {
|
func createBuildCommands(
|
||||||
|
context: XcodePluginContext,
|
||||||
|
target: XcodeTarget
|
||||||
|
) throws -> [Command] {
|
||||||
let inputFilePaths = target.inputFiles
|
let inputFilePaths = target.inputFiles
|
||||||
.filter { $0.type == .source && $0.path.extension == "swift" }
|
.filter { $0.type == .source && $0.path.extension == "swift" }
|
||||||
.map(\.path)
|
.map(\.path)
|
||||||
return createBuildCommands(
|
|
||||||
|
guard inputFilePaths.isEmpty == false else {
|
||||||
|
// Don't lint anything if there are no Swift source files in this target
|
||||||
|
return []
|
||||||
|
}
|
||||||
|
|
||||||
|
let swiftlint = try context.tool(named: "swiftlint")
|
||||||
|
var arguments: [String] = [
|
||||||
|
"lint",
|
||||||
|
"--cache-path", "\(context.pluginWorkDirectory)"
|
||||||
|
]
|
||||||
|
|
||||||
|
// Xcode build tool plugins don't seem to run from the project source directory, so our auto-discovery of
|
||||||
|
// configuration files doesn't work. We approximate it here.
|
||||||
|
if let configuration = context.xcodeProject.directory.firstConfigurationFileInParentDirectories() {
|
||||||
|
arguments.append(contentsOf: [
|
||||||
|
"--config", "\(configuration.string)"
|
||||||
|
])
|
||||||
|
}
|
||||||
|
|
||||||
|
arguments += inputFilePaths.map(\.string)
|
||||||
|
|
||||||
|
return [
|
||||||
|
.buildCommand(
|
||||||
|
displayName: "SwiftLint",
|
||||||
|
executable: swiftlint.path,
|
||||||
|
arguments: arguments,
|
||||||
inputFiles: inputFilePaths,
|
inputFiles: inputFilePaths,
|
||||||
packageDirectory: context.xcodeProject.directory,
|
outputFiles: [context.pluginWorkDirectory]
|
||||||
workingDirectory: context.pluginWorkDirectory,
|
|
||||||
tool: try context.tool(named: "swiftlint")
|
|
||||||
)
|
)
|
||||||
|
]
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
#endif
|
#endif
|
||||||
|
|
87
README.md
87
README.md
|
@ -1,6 +1,6 @@
|
||||||
# SwiftLint
|
# 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
|
SwiftLint hooks into [Clang](http://clang.llvm.org) and
|
||||||
[SourceKit](http://www.jpsim.com/uncovering-sourcekit) to use the
|
[SourceKit](http://www.jpsim.com/uncovering-sourcekit) to use the
|
||||||
|
@ -64,19 +64,13 @@ You can also build and install from source by cloning this project and running
|
||||||
|
|
||||||
### Using Bazel
|
### Using Bazel
|
||||||
|
|
||||||
Put this in your `MODULE.bazel`:
|
Put this in your `WORKSPACE`:
|
||||||
|
|
||||||
```bzl
|
|
||||||
bazel_dep(name = "swiftlint", version = "0.50.4", repo_name = "SwiftLint")
|
|
||||||
```
|
|
||||||
|
|
||||||
Or put this in your `WORKSPACE`:
|
|
||||||
|
|
||||||
<details>
|
<details>
|
||||||
|
|
||||||
<summary>WORKSPACE</summary>
|
<summary>WORKSPACE</summary>
|
||||||
|
|
||||||
```bzl
|
```python
|
||||||
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
load("@bazel_tools//tools/build_defs/repo:http.bzl", "http_archive")
|
||||||
|
|
||||||
http_archive(
|
http_archive(
|
||||||
|
@ -158,10 +152,7 @@ folder by default. To instruct Xcode where to find SwiftLint, you can either add
|
||||||
`/opt/homebrew/bin` to the `PATH` environment variable in your build phase
|
`/opt/homebrew/bin` to the `PATH` environment variable in your build phase
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
if [[ "$(uname -m)" == arm64 ]]; then
|
export PATH="$PATH:/opt/homebrew/bin"
|
||||||
export PATH="/opt/homebrew/bin:$PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if which swiftlint > /dev/null; then
|
if which swiftlint > /dev/null; then
|
||||||
swiftlint
|
swiftlint
|
||||||
else
|
else
|
||||||
|
@ -203,7 +194,7 @@ there is currently no way to pass any additional options to the SwiftLint execut
|
||||||
|
|
||||||
#### Xcode
|
#### 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.
|
with a project in Xcode.
|
||||||
|
|
||||||
Add SwiftLint as a package dependency to your project without linking any of the
|
Add SwiftLint as a package dependency to your project without linking any of the
|
||||||
|
@ -215,15 +206,6 @@ Select `SwiftLintPlugin` from the list and add it to the project.
|
||||||
|
|
||||||

|

|
||||||
|
|
||||||
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
|
#### Swift Package
|
||||||
|
|
||||||
You can integrate SwiftLint as a Swift Package Manager Plug-in if you're working with
|
You can integrate SwiftLint as a Swift Package Manager Plug-in if you're working with
|
||||||
|
@ -239,6 +221,14 @@ Add SwiftLint to a target using the `plugins` parameter.
|
||||||
),
|
),
|
||||||
```
|
```
|
||||||
|
|
||||||
|
|
||||||
|
### AppCode
|
||||||
|
|
||||||
|
To integrate SwiftLint with AppCode, install
|
||||||
|
[this plugin](https://plugins.jetbrains.com/plugin/9175) and configure
|
||||||
|
SwiftLint's installed path in the plugin's preferences.
|
||||||
|
The `fix` action is available via `⌥⏎`.
|
||||||
|
|
||||||
### Visual Studio Code
|
### Visual Studio Code
|
||||||
|
|
||||||
To integrate SwiftLint with [vscode](https://code.visualstudio.com), install the
|
To integrate SwiftLint with [vscode](https://code.visualstudio.com), install the
|
||||||
|
@ -308,7 +298,6 @@ SUBCOMMANDS:
|
||||||
docs Open SwiftLint documentation website in the default web browser
|
docs Open SwiftLint documentation website in the default web browser
|
||||||
generate-docs Generates markdown documentation for all rules
|
generate-docs Generates markdown documentation for all rules
|
||||||
lint (default) Print lint warnings and errors
|
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
|
rules Display the list of rules and their identifiers
|
||||||
version Display the current version of SwiftLint
|
version Display the current version of SwiftLint
|
||||||
|
|
||||||
|
@ -376,21 +365,12 @@ Once [installed](https://pre-commit.com/#install), add this to the
|
||||||
```yaml
|
```yaml
|
||||||
repos:
|
repos:
|
||||||
- repo: https://github.com/realm/SwiftLint
|
- repo: https://github.com/realm/SwiftLint
|
||||||
rev: 0.50.3
|
rev: 0.44.0
|
||||||
hooks:
|
hooks:
|
||||||
- id: swiftlint
|
- id: swiftlint
|
||||||
```
|
```
|
||||||
|
|
||||||
Adjust `rev` to the SwiftLint version of your choice. `pre-commit autoupdate` can be used to update to the current version.
|
Adjust `rev` to the SwiftLint version of your choice.
|
||||||
|
|
||||||
SwiftLint can be configured using `entry` to apply fixes and fail on errors:
|
|
||||||
```yaml
|
|
||||||
- repo: https://github.com/realm/SwiftLint
|
|
||||||
rev: 0.50.3
|
|
||||||
hooks:
|
|
||||||
- id: swiftlint
|
|
||||||
entry: swiftlint --fix --strict
|
|
||||||
```
|
|
||||||
|
|
||||||
## Rules
|
## Rules
|
||||||
|
|
||||||
|
@ -401,7 +381,7 @@ continues to contribute more over time.
|
||||||
You can find an updated list of rules and more information about them
|
You can find an updated list of rules and more information about them
|
||||||
[here](https://realm.github.io/SwiftLint/rule-directory.html).
|
[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.
|
directory to see their implementation.
|
||||||
|
|
||||||
### Opt-In Rules
|
### Opt-In Rules
|
||||||
|
@ -479,9 +459,7 @@ run SwiftLint from. The following parameters can be configured:
|
||||||
Rule inclusion:
|
Rule inclusion:
|
||||||
|
|
||||||
* `disabled_rules`: Disable rules from the default enabled set.
|
* `disabled_rules`: Disable rules from the default enabled set.
|
||||||
* `opt_in_rules`: Enable rules that are not part of the default set. The
|
* `opt_in_rules`: Enable rules that are not part of the default set.
|
||||||
special `all` identifier will enable all opt in linter rules, except the ones
|
|
||||||
listed in `disabled_rules`.
|
|
||||||
* `only_rules`: Only the rules specified in this list will be enabled.
|
* `only_rules`: Only the rules specified in this list will be enabled.
|
||||||
Cannot be specified alongside `disabled_rules` or `opt_in_rules`.
|
Cannot be specified alongside `disabled_rules` or `opt_in_rules`.
|
||||||
* `analyzer_rules`: This is an entirely separate list of rules that are only
|
* `analyzer_rules`: This is an entirely separate list of rules that are only
|
||||||
|
@ -503,9 +481,6 @@ opt_in_rules: # some rules are turned off by default, so you need to opt-in
|
||||||
# - empty_parameters
|
# - empty_parameters
|
||||||
# - vertical_whitespace
|
# - vertical_whitespace
|
||||||
|
|
||||||
analyzer_rules: # Rules run by `swiftlint analyze`
|
|
||||||
- explicit_self
|
|
||||||
|
|
||||||
included: # paths to include during linting. `--path` is ignored if present.
|
included: # paths to include during linting. `--path` is ignored if present.
|
||||||
- Source
|
- Source
|
||||||
excluded: # paths to ignore during linting. Takes precedence over `included`.
|
excluded: # paths to ignore during linting. Takes precedence over `included`.
|
||||||
|
@ -514,9 +489,8 @@ excluded: # paths to ignore during linting. Takes precedence over `included`.
|
||||||
- Source/ExcludedFolder
|
- Source/ExcludedFolder
|
||||||
- Source/ExcludedFile.swift
|
- Source/ExcludedFile.swift
|
||||||
- Source/*/ExcludedFile.swift # Exclude files with a wildcard
|
- Source/*/ExcludedFile.swift # Exclude files with a wildcard
|
||||||
|
analyzer_rules: # Rules run by `swiftlint analyze`
|
||||||
# If true, SwiftLint will not fail if no lintable files are found.
|
- explicit_self
|
||||||
allow_zero_lintable_files: false
|
|
||||||
|
|
||||||
# configurable rules can be customized from this configuration file
|
# configurable rules can be customized from this configuration file
|
||||||
# binary rules can set their severity level
|
# binary rules can set their severity level
|
||||||
|
@ -550,29 +524,13 @@ identifier_name:
|
||||||
- id
|
- id
|
||||||
- URL
|
- URL
|
||||||
- GlobalAPIKey
|
- 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,
|
You can also use environment variables in your configuration file,
|
||||||
by using `${SOME_VARIABLE}` in a string.
|
by using `${SOME_VARIABLE}` in a string.
|
||||||
|
|
||||||
### Defining Custom Rules
|
#### Defining Custom Rules
|
||||||
|
|
||||||
In addition to the rules that the main SwiftLint project ships with, SwiftLint
|
|
||||||
can also run two types of custom rules that you can define yourself in your own
|
|
||||||
projects:
|
|
||||||
|
|
||||||
#### 1. Swift Custom Rules
|
|
||||||
|
|
||||||
These rules are written the same way as the Swift-based rules that ship with
|
|
||||||
SwiftLint so they're fast, accurate, can leverage SwiftSyntax, can be unit
|
|
||||||
tested, and more.
|
|
||||||
|
|
||||||
Using these requires building SwiftLint with Bazel as described in
|
|
||||||
[this video](https://vimeo.com/820572803) or its associated code in
|
|
||||||
[github.com/jpsim/swiftlint-bazel-example](https://github.com/jpsim/swiftlint-bazel-example).
|
|
||||||
|
|
||||||
#### 2. Regex Custom Rules
|
|
||||||
|
|
||||||
You can define custom regex-based rules in your configuration file using the
|
You can define custom regex-based rules in your configuration file using the
|
||||||
following syntax:
|
following syntax:
|
||||||
|
@ -637,9 +595,6 @@ which match to `keyword` and `identifier` in the above list.
|
||||||
If using custom rules in combination with `only_rules`, make sure to add
|
If using custom rules in combination with `only_rules`, make sure to add
|
||||||
`custom_rules` as an item under `only_rules`.
|
`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
|
### Auto-correct
|
||||||
|
|
||||||
SwiftLint can automatically correct certain violations. Files on disk are
|
SwiftLint can automatically correct certain violations. Files on disk are
|
||||||
|
|
|
@ -1,6 +1,6 @@
|
||||||
# SwiftLint
|
# 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) 来表示源代码文件的更多精确结果。
|
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) 找到规则的更新列表和更多信息。
|
你可以在 [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` 默认是关闭的(即,你需要在你的配置文件中明确地打开它们)。
|
`opt_in_rules` 默认是关闭的(即,你需要在你的配置文件中明确地打开它们)。
|
||||||
|
|
||||||
|
|
11
README_KR.md
11
README_KR.md
|
@ -1,6 +1,6 @@
|
||||||
# SwiftLint
|
# 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) 표현을 사용합니다.
|
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` 환경 변수에 동시에 추가하여야 합니다.
|
그 이유는, 애플 실리콘 기반 맥에서 Homebrew는 기본적으로 바이너리들을 `/opt/homebrew/bin`에 저장하기 때문입니다. SwiftLint가 어디 있는지 찾는 것을 Xcode에 알려주기 위해, build phase에서 `/opt/homebrew/bin`를 `PATH` 환경 변수에 동시에 추가하여야 합니다.
|
||||||
|
|
||||||
```bash
|
```bash
|
||||||
if [[ "$(uname -m)" == arm64 ]]; then
|
export PATH="$PATH:/opt/homebrew/bin"
|
||||||
export PATH="/opt/homebrew/bin:$PATH"
|
|
||||||
fi
|
|
||||||
|
|
||||||
if which swiftlint > /dev/null; then
|
if which swiftlint > /dev/null; then
|
||||||
swiftlint
|
swiftlint
|
||||||
else
|
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`는 기본적으로 비활성화되어 있습니다. (즉, 설정 파일에서 명시적으로 해당 룰을 활성화해야 합니다.)
|
`opt_in_rules`는 기본적으로 비활성화되어 있습니다. (즉, 설정 파일에서 명시적으로 해당 룰을 활성화해야 합니다.)
|
||||||
|
|
||||||
|
|
17
Releasing.md
17
Releasing.md
|
@ -7,8 +7,19 @@ For SwiftLint contributors, follow these steps to cut a release:
|
||||||
* FabricSoftenerRule
|
* FabricSoftenerRule
|
||||||
* Top Loading
|
* Top Loading
|
||||||
* Fresh Out Of The Dryer
|
* Fresh Out Of The Dryer
|
||||||
|
1. Push new version: `make push_version "0.2.0: Tumble Dry"`
|
||||||
1. Make sure you have the latest stable Xcode version installed and
|
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. Create the pkg installer, framework zip, portable zip,
|
||||||
1. Wait for the Docker CI job to finish then run: `make zip_linux_release`
|
macos artifactbundle zip, and Linux zip:
|
||||||
|
`make release`
|
||||||
|
1. Create a GitHub release: https://github.com/realm/SwiftLint/releases/new
|
||||||
|
* Specify the tag you just pushed from the dropdown.
|
||||||
|
* Set the release title to the new version number & release name.
|
||||||
|
* Add the changelog section to the release description text box.
|
||||||
|
* Upload the bazel tarball & SHA-256 signature, pkg installer,
|
||||||
|
framework zip, portable zip, macos artifactbundle zip, and Linux zip
|
||||||
|
you just built to the GitHub release binaries.
|
||||||
|
* Click "Publish release".
|
||||||
|
1. Publish to Homebrew and CocoaPods trunk: `make publish`
|
||||||
1. Celebrate. :tada:
|
1. Celebrate. :tada:
|
||||||
|
|
|
@ -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__
|
|
|
@ -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
|
|
|
@ -1 +0,0 @@
|
||||||
@_exported import SwiftLintCore
|
|
|
@ -1,142 +0,0 @@
|
||||||
import SwiftSyntax
|
|
||||||
|
|
||||||
struct ExplicitTypeInterfaceRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule {
|
|
||||||
var configuration = ExplicitTypeInterfaceConfiguration()
|
|
||||||
|
|
||||||
static let description = RuleDescription(
|
|
||||||
identifier: "explicit_type_interface",
|
|
||||||
name: "Explicit Type Interface",
|
|
||||||
description: "Properties should have a type interface",
|
|
||||||
kind: .idiomatic,
|
|
||||||
nonTriggeringExamples: [
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
var myVar: Int? = 0
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
let myVar: Int? = 0, s: String = ""
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
static var myVar: Int? = 0
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
class var myVar: Int? = 0
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() {
|
|
||||||
if case .failure(let error) = errorCompletion {}
|
|
||||||
}
|
|
||||||
""", excludeFromDocumentation: true)
|
|
||||||
],
|
|
||||||
triggeringExamples: [
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
var ↓myVar = 0
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
let ↓mylet = 0
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
static var ↓myStaticVar = 0
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
class var ↓myClassVar = 0
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
let ↓myVar = Int(0), ↓s = ""
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class Foo {
|
|
||||||
let ↓myVar = Set<Int>(0)
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
||||||
Visitor(configuration: configuration)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Visitor: ViolationsSyntaxVisitor {
|
|
||||||
let configuration: ExplicitTypeInterfaceConfiguration
|
|
||||||
|
|
||||||
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
|
|
||||||
|
|
||||||
init(configuration: ExplicitTypeInterfaceConfiguration) {
|
|
||||||
self.configuration = configuration
|
|
||||||
super.init(viewMode: .sourceAccurate)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func visitPost(_ node: VariableDeclSyntax) {
|
|
||||||
if node.modifiers.isClass {
|
|
||||||
if configuration.allowedKinds.contains(.class) {
|
|
||||||
checkViolation(node)
|
|
||||||
}
|
|
||||||
} else if node.modifiers.isStatic {
|
|
||||||
if configuration.allowedKinds.contains(.static) {
|
|
||||||
checkViolation(node)
|
|
||||||
}
|
|
||||||
} else if node.parent?.is(MemberDeclListItemSyntax.self) == true {
|
|
||||||
if configuration.allowedKinds.contains(.instance) {
|
|
||||||
checkViolation(node)
|
|
||||||
}
|
|
||||||
} else if node.parent?.is(CodeBlockItemSyntax.self) == true {
|
|
||||||
if configuration.allowedKinds.contains(.local) {
|
|
||||||
checkViolation(node)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private func checkViolation(_ node: VariableDeclSyntax) {
|
|
||||||
for binding in node.bindings {
|
|
||||||
if configuration.allowRedundancy, let initializer = binding.initializer,
|
|
||||||
initializer.isTypeConstructor || initializer.isTypeReference {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if binding.typeAnnotation == nil {
|
|
||||||
violations.append(binding.positionAfterSkippingLeadingTrivia)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension InitializerClauseSyntax {
|
|
||||||
var isTypeConstructor: Bool {
|
|
||||||
if value.as(FunctionCallExprSyntax.self)?.callsPotentialType == true {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
if let tryExpr = value.as(TryExprSyntax.self),
|
|
||||||
tryExpr.expression.as(FunctionCallExprSyntax.self)?.callsPotentialType == true {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var isTypeReference: Bool {
|
|
||||||
value.as(MemberAccessExprSyntax.self)?.name.tokenKind == .keyword(.self)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension FunctionCallExprSyntax {
|
|
||||||
var callsPotentialType: Bool {
|
|
||||||
let name = calledExpression.debugDescription
|
|
||||||
return name.first?.isUppercase == true || (name.first == "[" && name.last == "]")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,129 +0,0 @@
|
||||||
import SwiftSyntax
|
|
||||||
|
|
||||||
struct NoMagicNumbersRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
|
|
||||||
var configuration = NoMagicNumbersConfiguration()
|
|
||||||
|
|
||||||
static let description = RuleDescription(
|
|
||||||
identifier: "no_magic_numbers",
|
|
||||||
name: "No Magic Numbers",
|
|
||||||
description: "Magic numbers should be replaced by named constants",
|
|
||||||
kind: .idiomatic,
|
|
||||||
nonTriggeringExamples: [
|
|
||||||
Example("var foo = 123"),
|
|
||||||
Example("static let bar: Double = 0.123"),
|
|
||||||
Example("let a = b + 1.0"),
|
|
||||||
Example("array[0] + array[1] "),
|
|
||||||
Example("let foo = 1_000.000_01"),
|
|
||||||
Example("// array[1337]"),
|
|
||||||
Example("baz(\"9999\")"),
|
|
||||||
Example("""
|
|
||||||
func foo() {
|
|
||||||
let x: Int = 2
|
|
||||||
let y = 3
|
|
||||||
let vector = [x, y, -1]
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class A {
|
|
||||||
var foo: Double = 132
|
|
||||||
static let bar: Double = 0.98
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
@available(iOS 13, *)
|
|
||||||
func version() {
|
|
||||||
if #available(iOS 13, OSX 10.10, *) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
enum Example: Int {
|
|
||||||
case positive = 2
|
|
||||||
case negative = -2
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class FooTests: XCTestCase {
|
|
||||||
let array: [Int] = []
|
|
||||||
let bar = array[42]
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
class FooTests: XCTestCase {
|
|
||||||
class Bar {
|
|
||||||
let array: [Int] = []
|
|
||||||
let bar = array[42]
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
],
|
|
||||||
triggeringExamples: [
|
|
||||||
Example("foo(↓321)"),
|
|
||||||
Example("bar(↓1_000.005_01)"),
|
|
||||||
Example("array[↓42]"),
|
|
||||||
Example("let box = array[↓12 + ↓14]"),
|
|
||||||
Example("let a = b + ↓2.0"),
|
|
||||||
Example("Color.primary.opacity(isAnimate ? ↓0.1 : ↓1.5)")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
||||||
Visitor(viewMode: .sourceAccurate, testParentClasses: configuration.testParentClasses)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension NoMagicNumbersRule {
|
|
||||||
final class Visitor: ViolationsSyntaxVisitor {
|
|
||||||
private let testParentClasses: Set<String>
|
|
||||||
|
|
||||||
init(viewMode: SyntaxTreeViewMode, testParentClasses: Set<String>) {
|
|
||||||
self.testParentClasses = testParentClasses
|
|
||||||
super.init(viewMode: viewMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func visitPost(_ node: FloatLiteralExprSyntax) {
|
|
||||||
if node.isMemberOfATestClass(testParentClasses) == false, node.floatingDigits.isMagicNumber {
|
|
||||||
violations.append(node.floatingDigits.positionAfterSkippingLeadingTrivia)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
override func visitPost(_ node: IntegerLiteralExprSyntax) {
|
|
||||||
if node.isMemberOfATestClass(testParentClasses) == false, node.digits.isMagicNumber {
|
|
||||||
violations.append(node.digits.positionAfterSkippingLeadingTrivia)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension TokenSyntax {
|
|
||||||
var isMagicNumber: Bool {
|
|
||||||
guard let number = Double(text.replacingOccurrences(of: "_", with: "")) else {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
if [0, 1].contains(number) {
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
guard let grandparent = parent?.parent else {
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
return !grandparent.is(InitializerClauseSyntax.self)
|
|
||||||
&& grandparent.as(PrefixOperatorExprSyntax.self)?.parent?.is(InitializerClauseSyntax.self) != true
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension ExprSyntaxProtocol {
|
|
||||||
func isMemberOfATestClass(_ testParentClasses: Set<String>) -> Bool {
|
|
||||||
var parent = parent
|
|
||||||
while parent != nil {
|
|
||||||
if
|
|
||||||
let classDecl = parent?.as(ClassDeclSyntax.self),
|
|
||||||
classDecl.isXCTestCase(testParentClasses)
|
|
||||||
{
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
parent = parent?.parent
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,252 +0,0 @@
|
||||||
import SwiftSyntax
|
|
||||||
|
|
||||||
struct StrictFilePrivateRule: OptInRule, ConfigurationProviderRule, SwiftSyntaxRule {
|
|
||||||
var configuration = SeverityConfiguration<Self>(.warning)
|
|
||||||
|
|
||||||
static let description = RuleDescription(
|
|
||||||
identifier: "strict_fileprivate",
|
|
||||||
name: "Strict Fileprivate",
|
|
||||||
description: "`fileprivate` should be avoided",
|
|
||||||
kind: .idiomatic,
|
|
||||||
nonTriggeringExamples: [
|
|
||||||
Example("extension String {}"),
|
|
||||||
Example("private extension String {}"),
|
|
||||||
Example("""
|
|
||||||
public
|
|
||||||
extension String {
|
|
||||||
var i: Int { 1 }
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
private enum E {
|
|
||||||
func f() {}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
public struct S {
|
|
||||||
internal let i: Int
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
open class C {
|
|
||||||
private func f() {}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
internal actor A {}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
struct S1: P {
|
|
||||||
fileprivate let i = 2, j = 1
|
|
||||||
}
|
|
||||||
struct S2: P {
|
|
||||||
fileprivate var (k, l) = (1, 3)
|
|
||||||
}
|
|
||||||
protocol P {
|
|
||||||
var j: Int { get }
|
|
||||||
var l: Int { get }
|
|
||||||
}
|
|
||||||
""", excludeFromDocumentation: true),
|
|
||||||
Example("""
|
|
||||||
class C: P<Int> {
|
|
||||||
fileprivate func f() {}
|
|
||||||
}
|
|
||||||
protocol P<T> {
|
|
||||||
func f()
|
|
||||||
}
|
|
||||||
""", excludeFromDocumentation: true)
|
|
||||||
] + ["actor", "class", "enum", "extension", "struct"].map { type in
|
|
||||||
Example("""
|
|
||||||
\(type) T: P<Int> {
|
|
||||||
fileprivate func f() {}
|
|
||||||
fileprivate let i = 3
|
|
||||||
public fileprivate(set) var l = 3
|
|
||||||
}
|
|
||||||
protocol P<T> {
|
|
||||||
func f()
|
|
||||||
var i: Int { get }
|
|
||||||
var l: Int { get set }
|
|
||||||
}
|
|
||||||
""", excludeFromDocumentation: true)
|
|
||||||
},
|
|
||||||
triggeringExamples: [
|
|
||||||
Example("""
|
|
||||||
↓fileprivate class C {
|
|
||||||
↓fileprivate func f() {}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
↓fileprivate extension String {
|
|
||||||
↓fileprivate var isSomething: Bool { self == "something" }
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
↓fileprivate actor A {
|
|
||||||
↓fileprivate let i = 1
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
↓fileprivate struct C {
|
|
||||||
↓fileprivate(set) var myInt = 4
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
struct Outter {
|
|
||||||
struct Inter {
|
|
||||||
↓fileprivate struct Inner {}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
↓fileprivate func f() {}
|
|
||||||
""", excludeFromDocumentation: true)
|
|
||||||
] + ["actor", "class", "enum", "extension", "struct"].map { type in
|
|
||||||
Example("""
|
|
||||||
\(type) T: P<Int> {
|
|
||||||
fileprivate func f() {}
|
|
||||||
↓fileprivate func g() {}
|
|
||||||
fileprivate let i = 2
|
|
||||||
public ↓fileprivate(set) var j: Int { 1 }
|
|
||||||
↓fileprivate let a = 3, b = 4
|
|
||||||
public ↓fileprivate(set) var k = 2
|
|
||||||
}
|
|
||||||
protocol P<T> {
|
|
||||||
func f()
|
|
||||||
var i: Int { get }
|
|
||||||
var k: Int { get }
|
|
||||||
}
|
|
||||||
protocol Q {
|
|
||||||
func g()
|
|
||||||
var j: Int { get }
|
|
||||||
}
|
|
||||||
""", excludeFromDocumentation: true)
|
|
||||||
}
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
||||||
Visitor(viewMode: .sourceAccurate, file: file.syntaxTree)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private enum ProtocolRequirementType: Equatable {
|
|
||||||
case method(String)
|
|
||||||
case getter(String)
|
|
||||||
case setter(String)
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension StrictFilePrivateRule {
|
|
||||||
final class ProtocolCollector: ViolationsSyntaxVisitor {
|
|
||||||
private(set) var protocols = [String: [ProtocolRequirementType]]()
|
|
||||||
private var currentProtocolName: String = ""
|
|
||||||
|
|
||||||
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { .allExcept(ProtocolDeclSyntax.self) }
|
|
||||||
|
|
||||||
override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind {
|
|
||||||
currentProtocolName = node.identifier.text
|
|
||||||
return .visitChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
override func visit(_ node: FunctionDeclSyntax) -> SyntaxVisitorContinueKind {
|
|
||||||
protocols[currentProtocolName, default: []].append(.method(node.identifier.text))
|
|
||||||
return .skipChildren
|
|
||||||
}
|
|
||||||
|
|
||||||
override func visit(_ node: VariableDeclSyntax) -> SyntaxVisitorContinueKind {
|
|
||||||
for binding in node.bindings {
|
|
||||||
guard let name = binding.pattern.as(IdentifierPatternSyntax.self)?.identifier.text,
|
|
||||||
case .accessors(let accessors) = binding.accessor else {
|
|
||||||
continue
|
|
||||||
}
|
|
||||||
if accessors.specifiesGetAccessor {
|
|
||||||
protocols[currentProtocolName, default: []].append(.getter(name))
|
|
||||||
}
|
|
||||||
if accessors.specifiesSetAccessor {
|
|
||||||
protocols[currentProtocolName, default: []].append(.setter(name))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return .skipChildren
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
final class Visitor: ViolationsSyntaxVisitor {
|
|
||||||
private let file: SourceFileSyntax
|
|
||||||
|
|
||||||
init(viewMode: SyntaxTreeViewMode, file: SourceFileSyntax) {
|
|
||||||
self.file = file
|
|
||||||
super.init(viewMode: viewMode)
|
|
||||||
}
|
|
||||||
|
|
||||||
private lazy var protocols = {
|
|
||||||
ProtocolCollector(viewMode: .sourceAccurate).walk(tree: file, handler: \.protocols)
|
|
||||||
}()
|
|
||||||
|
|
||||||
override func visitPost(_ node: DeclModifierSyntax) {
|
|
||||||
guard node.name.tokenKind == .keyword(.fileprivate), let grandparent = node.parent?.parent else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
guard grandparent.is(FunctionDeclSyntax.self) || grandparent.is(VariableDeclSyntax.self) else {
|
|
||||||
violations.append(node.positionAfterSkippingLeadingTrivia)
|
|
||||||
return
|
|
||||||
}
|
|
||||||
let protocolMethodNames = implementedTypesInDecl(of: node).flatMap { protocols[$0, default: []] }
|
|
||||||
if let funcDecl = grandparent.as(FunctionDeclSyntax.self),
|
|
||||||
protocolMethodNames.contains(.method(funcDecl.identifier.text)) {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let varDecl = grandparent.as(VariableDeclSyntax.self) {
|
|
||||||
let isSpecificForSetter = node.detail?.detail.tokenKind == .identifier("set")
|
|
||||||
let firstImplementingProtocol = varDecl.bindings
|
|
||||||
.flatMap { binding in
|
|
||||||
let pattern = binding.pattern
|
|
||||||
if let name = pattern.as(IdentifierPatternSyntax.self)?.identifier.text {
|
|
||||||
return [name]
|
|
||||||
}
|
|
||||||
if let tuple = pattern.as(TuplePatternSyntax.self) {
|
|
||||||
return tuple.elements.compactMap {
|
|
||||||
$0.pattern.as(IdentifierPatternSyntax.self)?.identifier.text
|
|
||||||
}
|
|
||||||
}
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
.first {
|
|
||||||
protocolMethodNames.contains(isSpecificForSetter ? .setter($0) : .getter($0))
|
|
||||||
}
|
|
||||||
if firstImplementingProtocol != nil {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
}
|
|
||||||
violations.append(node.positionAfterSkippingLeadingTrivia)
|
|
||||||
}
|
|
||||||
|
|
||||||
private func implementedTypesInDecl(of node: SyntaxProtocol?) -> [String] {
|
|
||||||
guard let node else {
|
|
||||||
queuedFatalError("Given node is nil. That should not happen.")
|
|
||||||
}
|
|
||||||
if node.is(SourceFileSyntax.self) {
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
if let actorDecl = node.as(ActorDeclSyntax.self) {
|
|
||||||
return actorDecl.inheritanceClause.inheritedTypeNames
|
|
||||||
}
|
|
||||||
if let classDecl = node.as(ClassDeclSyntax.self) {
|
|
||||||
return classDecl.inheritanceClause.inheritedTypeNames
|
|
||||||
}
|
|
||||||
if let enumDecl = node.as(EnumDeclSyntax.self) {
|
|
||||||
return enumDecl.inheritanceClause.inheritedTypeNames
|
|
||||||
}
|
|
||||||
if let extensionDecl = node.as(ExtensionDeclSyntax.self) {
|
|
||||||
return extensionDecl.inheritanceClause.inheritedTypeNames
|
|
||||||
}
|
|
||||||
if let structDecl = node.as(StructDeclSyntax.self) {
|
|
||||||
return structDecl.inheritanceClause.inheritedTypeNames
|
|
||||||
}
|
|
||||||
return implementedTypesInDecl(of: node.parent)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension TypeInheritanceClauseSyntax? {
|
|
||||||
var inheritedTypeNames: [String] {
|
|
||||||
self?.inheritedTypeCollection.compactMap { $0.typeName.as(SimpleTypeIdentifierSyntax.self)?.name.text } ?? []
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,116 +0,0 @@
|
||||||
import SwiftSyntax
|
|
||||||
|
|
||||||
// TODO: [12/23/2024] Remove deprecation warning after ~2 years.
|
|
||||||
private let warnDeprecatedOnceImpl: Void = {
|
|
||||||
Issue.ruleDeprecated(ruleID: InertDeferRule.description.identifier).print()
|
|
||||||
}()
|
|
||||||
|
|
||||||
private func warnDeprecatedOnce() {
|
|
||||||
_ = warnDeprecatedOnceImpl
|
|
||||||
}
|
|
||||||
|
|
||||||
struct InertDeferRule: ConfigurationProviderRule, SwiftSyntaxRule, OptInRule {
|
|
||||||
var configuration = SeverityConfiguration<Self>(.warning)
|
|
||||||
|
|
||||||
static let description = RuleDescription(
|
|
||||||
identifier: "inert_defer",
|
|
||||||
name: "Inert Defer",
|
|
||||||
description: "If defer is at the end of its parent scope, it will be executed right where it is anyway",
|
|
||||||
kind: .lint,
|
|
||||||
nonTriggeringExamples: [
|
|
||||||
Example("""
|
|
||||||
func example3() {
|
|
||||||
defer { /* deferred code */ }
|
|
||||||
|
|
||||||
print("other code")
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func example4() {
|
|
||||||
if condition {
|
|
||||||
defer { /* deferred code */ }
|
|
||||||
print("other code")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() {
|
|
||||||
#if os(macOS)
|
|
||||||
defer { print(2) }
|
|
||||||
#else
|
|
||||||
defer { print(3) }
|
|
||||||
#endif
|
|
||||||
print(1)
|
|
||||||
}
|
|
||||||
""", excludeFromDocumentation: true)
|
|
||||||
],
|
|
||||||
triggeringExamples: [
|
|
||||||
Example("""
|
|
||||||
func example0() {
|
|
||||||
↓defer { /* deferred code */ }
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func example1() {
|
|
||||||
↓defer { /* deferred code */ }
|
|
||||||
// comment
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func example2() {
|
|
||||||
if condition {
|
|
||||||
↓defer { /* deferred code */ }
|
|
||||||
// comment
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f(arg: Int) {
|
|
||||||
if arg == 1 {
|
|
||||||
↓defer { print(2) }
|
|
||||||
// a comment
|
|
||||||
} else {
|
|
||||||
↓defer { print(3) }
|
|
||||||
}
|
|
||||||
print(1)
|
|
||||||
#if os(macOS)
|
|
||||||
↓defer { print(4) }
|
|
||||||
#else
|
|
||||||
↓defer { print(5) }
|
|
||||||
#endif
|
|
||||||
}
|
|
||||||
""", excludeFromDocumentation: true)
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
||||||
warnDeprecatedOnce()
|
|
||||||
return Visitor(viewMode: .sourceAccurate)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension InertDeferRule {
|
|
||||||
final class Visitor: ViolationsSyntaxVisitor {
|
|
||||||
override func visitPost(_ node: DeferStmtSyntax) {
|
|
||||||
guard node.isLastStatement else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
if let ifConfigClause = node.parent?.parent?.parent?.as(IfConfigClauseSyntax.self),
|
|
||||||
ifConfigClause.parent?.parent?.isLastStatement == false {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
violations.append(node.deferKeyword.positionAfterSkippingLeadingTrivia)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension SyntaxProtocol {
|
|
||||||
var isLastStatement: Bool {
|
|
||||||
if let codeBlockItem = parent?.as(CodeBlockItemSyntax.self),
|
|
||||||
let codeBlockList = codeBlockItem.parent?.as(CodeBlockItemListSyntax.self) {
|
|
||||||
return codeBlockList.last == codeBlockItem
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,92 +0,0 @@
|
||||||
import Foundation
|
|
||||||
import SourceKittenFramework
|
|
||||||
import SwiftIDEUtils
|
|
||||||
|
|
||||||
struct PeriodSpacingRule: SourceKitFreeRule, ConfigurationProviderRule, OptInRule, SubstitutionCorrectableRule {
|
|
||||||
var configuration = SeverityConfiguration<Self>(.warning)
|
|
||||||
|
|
||||||
static let description = RuleDescription(
|
|
||||||
identifier: "period_spacing",
|
|
||||||
name: "Period Spacing",
|
|
||||||
description: "Periods should not be followed by more than one space",
|
|
||||||
kind: .style,
|
|
||||||
nonTriggeringExamples: [
|
|
||||||
Example("let pi = 3.2"),
|
|
||||||
Example("let pi = Double.pi"),
|
|
||||||
Example("let pi = Double. pi"),
|
|
||||||
Example("let pi = Double. pi"),
|
|
||||||
Example("// A. Single."),
|
|
||||||
Example("/// - code: Identifier of the error. Integer."),
|
|
||||||
Example("""
|
|
||||||
// value: Multiline.
|
|
||||||
// Comment.
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
/**
|
|
||||||
Sentence ended in period.
|
|
||||||
|
|
||||||
- Sentence 2 new line characters after.
|
|
||||||
**/
|
|
||||||
""")
|
|
||||||
],
|
|
||||||
triggeringExamples: [
|
|
||||||
Example("/* Only god knows why. ↓ This symbol does nothing. */", testWrappingInComment: false),
|
|
||||||
Example("// Only god knows why. ↓ This symbol does nothing.", testWrappingInComment: false),
|
|
||||||
Example("// Single. Double. ↓ End.", testWrappingInComment: false),
|
|
||||||
Example("// Single. Double. ↓ Triple. ↓ End.", testWrappingInComment: false),
|
|
||||||
Example("// Triple. ↓ Quad. ↓ End.", testWrappingInComment: false),
|
|
||||||
Example("/// - code: Identifier of the error. ↓ Integer.", testWrappingInComment: false)
|
|
||||||
],
|
|
||||||
corrections: [
|
|
||||||
Example("/* Why. ↓ Symbol does nothing. */"): Example("/* Why. Symbol does nothing. */"),
|
|
||||||
Example("// Why. ↓ Symbol does nothing."): Example("// Why. Symbol does nothing."),
|
|
||||||
Example("// Single. Double. ↓ End."): Example("// Single. Double. End."),
|
|
||||||
Example("// Single. Double. ↓ Triple. ↓ End."): Example("// Single. Double. Triple. End."),
|
|
||||||
Example("// Triple. ↓ Quad. ↓ End."): Example("// Triple. Quad. End."),
|
|
||||||
Example("/// - code: Identifier. ↓ Integer."): Example("/// - code: Identifier. Integer.")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
func violationRanges(in file: SwiftLintFile) -> [NSRange] {
|
|
||||||
// Find all comment tokens in the file and regex search them for violations
|
|
||||||
file.syntaxClassifications
|
|
||||||
.filter(\.kind.isComment)
|
|
||||||
.map { $0.range.toSourceKittenByteRange() }
|
|
||||||
.compactMap { (range: ByteRange) -> [NSRange]? in
|
|
||||||
return file.stringView
|
|
||||||
.substringWithByteRange(range)
|
|
||||||
.map(StringView.init)
|
|
||||||
.map { commentBody in
|
|
||||||
// Look for a period followed by two or more whitespaces but not new line or carriage returns
|
|
||||||
return regex(#"\.[^\S\r\n]{2,}"#)
|
|
||||||
.matches(in: commentBody)
|
|
||||||
.compactMap { result in
|
|
||||||
// Set the location to start from the second whitespace till the last one.
|
|
||||||
return file.stringView.byteRangeToNSRange(
|
|
||||||
ByteRange(
|
|
||||||
// Safe to mix NSRange offsets with byte offsets here because the
|
|
||||||
// regex can't contain multi-byte characters
|
|
||||||
location: ByteCount(range.lowerBound.value + result.range.lowerBound + 2),
|
|
||||||
length: ByteCount(result.range.length.advanced(by: -2))
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
.flatMap { $0 }
|
|
||||||
}
|
|
||||||
|
|
||||||
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
|
||||||
return violationRanges(in: file).map { range in
|
|
||||||
StyleViolation(
|
|
||||||
ruleDescription: Self.description,
|
|
||||||
severity: configuration.severity,
|
|
||||||
location: Location(file: file, characterOffset: range.location)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
func substitution(for violationRange: NSRange, in file: SwiftLintFile) -> (NSRange, String)? {
|
|
||||||
return (violationRange, "")
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,45 +0,0 @@
|
||||||
struct ExplicitTypeInterfaceConfiguration: SeverityBasedRuleConfiguration, Equatable {
|
|
||||||
typealias Parent = ExplicitTypeInterfaceRule
|
|
||||||
|
|
||||||
enum VariableKind: String, CaseIterable {
|
|
||||||
case instance
|
|
||||||
case local
|
|
||||||
case `static`
|
|
||||||
case `class`
|
|
||||||
|
|
||||||
static let all = Set(allCases)
|
|
||||||
}
|
|
||||||
|
|
||||||
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
|
|
||||||
|
|
||||||
private(set) var allowedKinds = VariableKind.all
|
|
||||||
|
|
||||||
private(set) var allowRedundancy = false
|
|
||||||
|
|
||||||
var consoleDescription: String {
|
|
||||||
let excludedKinds = VariableKind.all.subtracting(allowedKinds).map(\.rawValue).sorted()
|
|
||||||
return "severity: \(severityConfiguration.consoleDescription)" +
|
|
||||||
", excluded: \(excludedKinds)" +
|
|
||||||
", allow_redundancy: \(allowRedundancy)"
|
|
||||||
}
|
|
||||||
|
|
||||||
init() {}
|
|
||||||
|
|
||||||
mutating func apply(configuration: Any) throws {
|
|
||||||
guard let configuration = configuration as? [String: Any] else {
|
|
||||||
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
|
|
||||||
}
|
|
||||||
for (key, value) in configuration {
|
|
||||||
switch (key, value) {
|
|
||||||
case ("severity", let severityString as String):
|
|
||||||
try severityConfiguration.apply(configuration: severityString)
|
|
||||||
case ("excluded", let excludedStrings as [String]):
|
|
||||||
allowedKinds.subtract(excludedStrings.compactMap(VariableKind.init(rawValue:)))
|
|
||||||
case ("allow_redundancy", let allowRedundancy as Bool):
|
|
||||||
self.allowRedundancy = allowRedundancy
|
|
||||||
default:
|
|
||||||
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,108 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
struct NameConfiguration<Parent: Rule>: RuleConfiguration, Equatable {
|
|
||||||
typealias Severity = SeverityConfiguration<Parent>
|
|
||||||
typealias SeverityLevels = SeverityLevelsConfiguration<Parent>
|
|
||||||
typealias StartWithLowercaseConfiguration = ChildOptionSeverityConfiguration<Parent>
|
|
||||||
|
|
||||||
var consoleDescription: String {
|
|
||||||
"(min_length) \(minLength.shortConsoleDescription), " +
|
|
||||||
"(max_length) \(maxLength.shortConsoleDescription), " +
|
|
||||||
"excluded: \(excludedRegularExpressions.map { $0.pattern }.sorted()), " +
|
|
||||||
"allowed_symbols: \(allowedSymbols.sorted()), " +
|
|
||||||
"validates_start_with_lowercase: \(validatesStartWithLowercase.consoleDescription)"
|
|
||||||
}
|
|
||||||
|
|
||||||
private(set) var minLength: SeverityLevels
|
|
||||||
private(set) var maxLength: SeverityLevels
|
|
||||||
private(set) var excludedRegularExpressions: Set<NSRegularExpression>
|
|
||||||
private(set) var validatesStartWithLowercase: StartWithLowercaseConfiguration
|
|
||||||
private(set) var allowedSymbols: Set<String>
|
|
||||||
|
|
||||||
var minLengthThreshold: Int {
|
|
||||||
return max(minLength.warning, minLength.error ?? minLength.warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
var maxLengthThreshold: Int {
|
|
||||||
return min(maxLength.warning, maxLength.error ?? maxLength.warning)
|
|
||||||
}
|
|
||||||
|
|
||||||
var allowedSymbolsAndAlphanumerics: CharacterSet {
|
|
||||||
CharacterSet(charactersIn: allowedSymbols.joined()).union(.alphanumerics)
|
|
||||||
}
|
|
||||||
|
|
||||||
init(minLengthWarning: Int,
|
|
||||||
minLengthError: Int,
|
|
||||||
maxLengthWarning: Int,
|
|
||||||
maxLengthError: Int,
|
|
||||||
excluded: [String] = [],
|
|
||||||
allowedSymbols: [String] = [],
|
|
||||||
validatesStartWithLowercase: StartWithLowercaseConfiguration = .error) {
|
|
||||||
minLength = SeverityLevels(warning: minLengthWarning, error: minLengthError)
|
|
||||||
maxLength = SeverityLevels(warning: maxLengthWarning, error: maxLengthError)
|
|
||||||
self.excludedRegularExpressions = Set(excluded.compactMap {
|
|
||||||
try? NSRegularExpression.cached(pattern: "^\($0)$")
|
|
||||||
})
|
|
||||||
self.allowedSymbols = Set(allowedSymbols)
|
|
||||||
self.validatesStartWithLowercase = validatesStartWithLowercase
|
|
||||||
}
|
|
||||||
|
|
||||||
mutating func apply(configuration: Any) throws {
|
|
||||||
guard let configurationDict = configuration as? [String: Any] else {
|
|
||||||
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let minLengthConfiguration = configurationDict["min_length"] {
|
|
||||||
try minLength.apply(configuration: minLengthConfiguration)
|
|
||||||
}
|
|
||||||
if let maxLengthConfiguration = configurationDict["max_length"] {
|
|
||||||
try maxLength.apply(configuration: maxLengthConfiguration)
|
|
||||||
}
|
|
||||||
if let excluded = [String].array(of: configurationDict["excluded"]) {
|
|
||||||
self.excludedRegularExpressions = Set(excluded.compactMap {
|
|
||||||
try? NSRegularExpression.cached(pattern: "^\($0)$")
|
|
||||||
})
|
|
||||||
}
|
|
||||||
if let allowedSymbols = [String].array(of: configurationDict["allowed_symbols"]) {
|
|
||||||
self.allowedSymbols = Set(allowedSymbols)
|
|
||||||
}
|
|
||||||
|
|
||||||
if let validatesStartWithLowercase = configurationDict["validates_start_with_lowercase"] as? String {
|
|
||||||
try self.validatesStartWithLowercase.apply(configuration: validatesStartWithLowercase)
|
|
||||||
} else if let validatesStartWithLowercase = configurationDict["validates_start_with_lowercase"] as? Bool {
|
|
||||||
// TODO: [05/10/2025] Remove deprecation warning after ~2 years.
|
|
||||||
self.validatesStartWithLowercase = validatesStartWithLowercase ? .error : .off
|
|
||||||
Issue.genericWarning(
|
|
||||||
"""
|
|
||||||
The \"validates_start_with_lowercase\" configuration now expects a severity (warning or \
|
|
||||||
error). The boolean value 'true' will still enable it as an error. It is now deprecated and will be \
|
|
||||||
removed in a future release.
|
|
||||||
"""
|
|
||||||
).print()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
extension NameConfiguration {
|
|
||||||
func severity(forLength length: Int) -> ViolationSeverity? {
|
|
||||||
if let minError = minLength.error, length < minError {
|
|
||||||
return .error
|
|
||||||
} else if let maxError = maxLength.error, length > maxError {
|
|
||||||
return .error
|
|
||||||
} else if length < minLength.warning ||
|
|
||||||
length > maxLength.warning {
|
|
||||||
return .warning
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - `exclude` option extensions
|
|
||||||
|
|
||||||
extension NameConfiguration {
|
|
||||||
func shouldExclude(name: String) -> Bool {
|
|
||||||
excludedRegularExpressions.contains {
|
|
||||||
!$0.matches(in: name, options: [], range: NSRange(name.startIndex..., in: name)).isEmpty
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,22 +0,0 @@
|
||||||
struct PrefixedTopLevelConstantConfiguration: SeverityBasedRuleConfiguration, Equatable {
|
|
||||||
typealias Parent = PrefixedTopLevelConstantRule
|
|
||||||
|
|
||||||
private(set) var severityConfiguration = SeverityConfiguration<Parent>(.warning)
|
|
||||||
private(set) var onlyPrivateMembers = false
|
|
||||||
|
|
||||||
var consoleDescription: String {
|
|
||||||
return "severity: \(severityConfiguration.consoleDescription)" + ", only_private: \(onlyPrivateMembers)"
|
|
||||||
}
|
|
||||||
|
|
||||||
mutating func apply(configuration: Any) throws {
|
|
||||||
guard let configuration = configuration as? [String: Any] else {
|
|
||||||
throw Issue.unknownConfiguration(ruleID: Parent.identifier)
|
|
||||||
}
|
|
||||||
|
|
||||||
onlyPrivateMembers = (configuration["only_private"] as? Bool == true)
|
|
||||||
|
|
||||||
if let severityString = configuration["severity"] as? String {
|
|
||||||
try severityConfiguration.apply(configuration: severityString)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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:)))
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,263 +0,0 @@
|
||||||
import SwiftSyntax
|
|
||||||
|
|
||||||
struct DirectReturnRule: SwiftSyntaxCorrectableRule, ConfigurationProviderRule, OptInRule {
|
|
||||||
var configuration = SeverityConfiguration<Self>(.warning)
|
|
||||||
|
|
||||||
static var description = RuleDescription(
|
|
||||||
identifier: "direct_return",
|
|
||||||
name: "Direct Return",
|
|
||||||
description: "Directly return the expression instead of assigning it to a variable first",
|
|
||||||
kind: .style,
|
|
||||||
nonTriggeringExamples: [
|
|
||||||
Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
let b = 2
|
|
||||||
let a = 1
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
struct S {
|
|
||||||
var a: Int {
|
|
||||||
var b = 1
|
|
||||||
b = 2
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
let b = 2
|
|
||||||
f()
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
{ i in
|
|
||||||
let b = 2
|
|
||||||
return i
|
|
||||||
}(1)
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
],
|
|
||||||
triggeringExamples: [
|
|
||||||
Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
let ↓b = 2
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
struct S {
|
|
||||||
var a: Int {
|
|
||||||
var ↓b = 1
|
|
||||||
// comment
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() -> Bool {
|
|
||||||
let a = 1, ↓b = true
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
{ _ in
|
|
||||||
let ↓b = 2
|
|
||||||
return b
|
|
||||||
}(1)
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f(i: Int) -> Int {
|
|
||||||
if i > 1 {
|
|
||||||
let ↓a = 2
|
|
||||||
return a
|
|
||||||
} else {
|
|
||||||
let ↓b = 2, a = 1
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
],
|
|
||||||
corrections: [
|
|
||||||
Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
let b = 2
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
"""): Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
return 2
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
struct S {
|
|
||||||
var a: Int {
|
|
||||||
var b = 2 > 1
|
|
||||||
? f()
|
|
||||||
: 1_000
|
|
||||||
// comment
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
func f() -> Int { 1 }
|
|
||||||
}
|
|
||||||
"""): Example("""
|
|
||||||
struct S {
|
|
||||||
var a: Int {
|
|
||||||
// comment
|
|
||||||
return 2 > 1
|
|
||||||
? f()
|
|
||||||
: 1_000
|
|
||||||
}
|
|
||||||
func f() -> Int { 1 }
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() -> Bool {
|
|
||||||
let a = 1, b = true
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
"""): Example("""
|
|
||||||
func f() -> Bool {
|
|
||||||
let a = 1
|
|
||||||
return true
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
{ _ in
|
|
||||||
// A comment
|
|
||||||
let b = 2
|
|
||||||
// Another comment
|
|
||||||
return b
|
|
||||||
}(1)
|
|
||||||
}
|
|
||||||
"""): Example("""
|
|
||||||
func f() -> Int {
|
|
||||||
{ _ in
|
|
||||||
// A comment
|
|
||||||
// Another comment
|
|
||||||
return 2
|
|
||||||
}(1)
|
|
||||||
}
|
|
||||||
"""),
|
|
||||||
Example("""
|
|
||||||
func f() -> Bool {
|
|
||||||
let b : Bool = true
|
|
||||||
return b
|
|
||||||
}
|
|
||||||
"""): Example("""
|
|
||||||
func f() -> Bool {
|
|
||||||
return true as Bool
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
]
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
||||||
Visitor(viewMode: .sourceAccurate)
|
|
||||||
}
|
|
||||||
|
|
||||||
func makeRewriter(file: SwiftLintFile) -> ViolationsSyntaxRewriter? {
|
|
||||||
Rewriter(
|
|
||||||
locationConverter: file.locationConverter,
|
|
||||||
disabledRegions: disabledRegions(file: file)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private class Visitor: ViolationsSyntaxVisitor {
|
|
||||||
override var skippableDeclarations: [DeclSyntaxProtocol.Type] { [ProtocolDeclSyntax.self] }
|
|
||||||
|
|
||||||
override func visitPost(_ statements: CodeBlockItemListSyntax) {
|
|
||||||
if let (binding, _) = statements.violation {
|
|
||||||
violations.append(binding.positionAfterSkippingLeadingTrivia)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension CodeBlockItemListSyntax {
|
|
||||||
var violation: (PatternBindingSyntax, ReturnStmtSyntax)? {
|
|
||||||
guard count >= 2, let last = last?.item,
|
|
||||||
let returnStmt = last.as(ReturnStmtSyntax.self),
|
|
||||||
let identifier = returnStmt.expression?.as(IdentifierExprSyntax.self)?.identifier.text,
|
|
||||||
let varDecl = dropLast().last?.item.as(VariableDeclSyntax.self) else {
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
let binding = varDecl.bindings.first {
|
|
||||||
$0.pattern.as(IdentifierPatternSyntax.self)?.identifier.text == identifier
|
|
||||||
}
|
|
||||||
if let binding {
|
|
||||||
return (binding, returnStmt)
|
|
||||||
}
|
|
||||||
return nil
|
|
||||||
}
|
|
||||||
}
|
|
||||||
private class Rewriter: SyntaxRewriter, ViolationsSyntaxRewriter {
|
|
||||||
private(set) var correctionPositions: [AbsolutePosition] = []
|
|
||||||
let locationConverter: SourceLocationConverter
|
|
||||||
let disabledRegions: [SourceRange]
|
|
||||||
|
|
||||||
init(locationConverter: SourceLocationConverter, disabledRegions: [SourceRange]) {
|
|
||||||
self.locationConverter = locationConverter
|
|
||||||
self.disabledRegions = disabledRegions
|
|
||||||
}
|
|
||||||
|
|
||||||
override func visit(_ statements: CodeBlockItemListSyntax) -> CodeBlockItemListSyntax {
|
|
||||||
guard let (binding, returnStmt) = statements.violation,
|
|
||||||
!binding.isContainedIn(regions: disabledRegions, locationConverter: locationConverter),
|
|
||||||
let bindingList = binding.parent?.as(PatternBindingListSyntax.self),
|
|
||||||
let varDecl = bindingList.parent?.as(VariableDeclSyntax.self),
|
|
||||||
var initExpression = binding.initializer?.value else {
|
|
||||||
return super.visit(statements)
|
|
||||||
}
|
|
||||||
correctionPositions.append(binding.positionAfterSkippingLeadingTrivia)
|
|
||||||
var newStmtList = Array(statements.dropLast(2))
|
|
||||||
let newBindingList = bindingList
|
|
||||||
.filter { $0 != binding }
|
|
||||||
.enumerated()
|
|
||||||
.map { index, item in
|
|
||||||
if index == bindingList.count - 2 {
|
|
||||||
return item.with(\.trailingComma, nil)
|
|
||||||
}
|
|
||||||
return item
|
|
||||||
}
|
|
||||||
if let type = binding.typeAnnotation?.type {
|
|
||||||
initExpression = ExprSyntax(
|
|
||||||
fromProtocol: AsExprSyntax(
|
|
||||||
expression: initExpression.trimmed,
|
|
||||||
asTok: .keyword(.as).with(\.leadingTrivia, .space).with(\.trailingTrivia, .space),
|
|
||||||
typeName: type.trimmed
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
if newBindingList.isNotEmpty {
|
|
||||||
newStmtList.append(CodeBlockItemSyntax(
|
|
||||||
item: .decl(DeclSyntax(varDecl.with(\.bindings, PatternBindingListSyntax(newBindingList))))
|
|
||||||
))
|
|
||||||
newStmtList.append(CodeBlockItemSyntax(
|
|
||||||
item: .stmt(StmtSyntax(returnStmt.with(\.expression, initExpression)))
|
|
||||||
))
|
|
||||||
} else {
|
|
||||||
let leadingTrivia = varDecl.leadingTrivia.withoutTrailingIndentation +
|
|
||||||
varDecl.trailingTrivia +
|
|
||||||
returnStmt.leadingTrivia.withFirstEmptyLineRemoved
|
|
||||||
newStmtList.append(
|
|
||||||
CodeBlockItemSyntax(
|
|
||||||
item: .stmt(
|
|
||||||
StmtSyntax(
|
|
||||||
returnStmt
|
|
||||||
.with(\.expression, initExpression)
|
|
||||||
.with(\.leadingTrivia, leadingTrivia)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
return super.visit(CodeBlockItemListSyntax(newStmtList))
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,128 +0,0 @@
|
||||||
import SwiftSyntax
|
|
||||||
|
|
||||||
struct MultilineArgumentsRule: SwiftSyntaxRule, OptInRule, ConfigurationProviderRule {
|
|
||||||
var configuration = MultilineArgumentsConfiguration()
|
|
||||||
|
|
||||||
static let description = RuleDescription(
|
|
||||||
identifier: "multiline_arguments",
|
|
||||||
name: "Multiline Arguments",
|
|
||||||
description: "Arguments should be either on the same line, or one per line",
|
|
||||||
kind: .style,
|
|
||||||
nonTriggeringExamples: MultilineArgumentsRuleExamples.nonTriggeringExamples,
|
|
||||||
triggeringExamples: MultilineArgumentsRuleExamples.triggeringExamples
|
|
||||||
)
|
|
||||||
|
|
||||||
func makeVisitor(file: SwiftLintFile) -> ViolationsSyntaxVisitor {
|
|
||||||
Visitor(
|
|
||||||
onlyEnforceAfterFirstClosureOnFirstLine: configuration.onlyEnforceAfterFirstClosureOnFirstLine,
|
|
||||||
firstArgumentLocation: configuration.firstArgumentLocation,
|
|
||||||
locationConverter: file.locationConverter
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private extension MultilineArgumentsRule {
|
|
||||||
final class Visitor: ViolationsSyntaxVisitor {
|
|
||||||
let onlyEnforceAfterFirstClosureOnFirstLine: Bool
|
|
||||||
let firstArgumentLocation: MultilineArgumentsConfiguration.FirstArgumentLocation
|
|
||||||
let locationConverter: SourceLocationConverter
|
|
||||||
|
|
||||||
init(onlyEnforceAfterFirstClosureOnFirstLine: Bool,
|
|
||||||
firstArgumentLocation: MultilineArgumentsConfiguration.FirstArgumentLocation,
|
|
||||||
locationConverter: SourceLocationConverter) {
|
|
||||||
self.onlyEnforceAfterFirstClosureOnFirstLine = onlyEnforceAfterFirstClosureOnFirstLine
|
|
||||||
self.firstArgumentLocation = firstArgumentLocation
|
|
||||||
self.locationConverter = locationConverter
|
|
||||||
super.init(viewMode: .sourceAccurate)
|
|
||||||
}
|
|
||||||
|
|
||||||
override func visitPost(_ node: FunctionCallExprSyntax) {
|
|
||||||
guard node.argumentList.count > 1 else {
|
|
||||||
return
|
|
||||||
}
|
|
||||||
|
|
||||||
let functionCallPosition = node.calledExpression.positionAfterSkippingLeadingTrivia
|
|
||||||
let functionCallLine = locationConverter.location(for: functionCallPosition).line
|
|
||||||
let wrappedArguments: [Argument] = node.argumentList
|
|
||||||
.enumerated()
|
|
||||||
.compactMap { idx, argument in
|
|
||||||
Argument(element: argument, locationConverter: locationConverter, index: idx)
|
|
||||||
}
|
|
||||||
|
|
||||||
var violatingArguments = findViolations(in: wrappedArguments, functionCallLine: functionCallLine)
|
|
||||||
|
|
||||||
if onlyEnforceAfterFirstClosureOnFirstLine {
|
|
||||||
violatingArguments = removeViolationsBeforeFirstClosure(arguments: wrappedArguments,
|
|
||||||
violations: violatingArguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
violations.append(contentsOf: violatingArguments.map(\.offset))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Violation Logic
|
|
||||||
|
|
||||||
private func findViolations(in arguments: [Argument],
|
|
||||||
functionCallLine: Int) -> [Argument] {
|
|
||||||
var visitedLines = Set<Int>()
|
|
||||||
|
|
||||||
if firstArgumentLocation == .sameLine {
|
|
||||||
visitedLines.insert(functionCallLine)
|
|
||||||
}
|
|
||||||
|
|
||||||
let violations = arguments.compactMap { argument -> Argument? in
|
|
||||||
let (line, idx) = (argument.line, argument.index)
|
|
||||||
let (firstVisit, _) = visitedLines.insert(line)
|
|
||||||
|
|
||||||
if idx == 0 {
|
|
||||||
switch firstArgumentLocation {
|
|
||||||
case .anyLine: return nil
|
|
||||||
case .nextLine: return line > functionCallLine ? nil : argument
|
|
||||||
case .sameLine: return line > functionCallLine ? argument : nil
|
|
||||||
}
|
|
||||||
} else {
|
|
||||||
return firstVisit ? nil : argument
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// only report violations if multiline
|
|
||||||
return visitedLines.count > 1 ? violations : []
|
|
||||||
}
|
|
||||||
|
|
||||||
private func removeViolationsBeforeFirstClosure(arguments: [Argument],
|
|
||||||
violations: [Argument]) -> [Argument] {
|
|
||||||
guard let firstClosure = arguments.first(where: { $0.isClosure }),
|
|
||||||
let firstArgument = arguments.first else {
|
|
||||||
return violations
|
|
||||||
}
|
|
||||||
|
|
||||||
let violationSlice: ArraySlice<Argument> = violations
|
|
||||||
.drop { argument in
|
|
||||||
// drop violations if they precede the first closure,
|
|
||||||
// if that closure is in the first line
|
|
||||||
firstArgument.line == firstClosure.line &&
|
|
||||||
argument.line == firstClosure.line &&
|
|
||||||
argument.index <= firstClosure.index
|
|
||||||
}
|
|
||||||
|
|
||||||
return Array(violationSlice)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
private struct Argument {
|
|
||||||
let offset: AbsolutePosition
|
|
||||||
let line: Int
|
|
||||||
let index: Int
|
|
||||||
let expression: ExprSyntax
|
|
||||||
|
|
||||||
init?(element: TupleExprElementSyntax, locationConverter: SourceLocationConverter, index: Int) {
|
|
||||||
self.offset = element.positionAfterSkippingLeadingTrivia
|
|
||||||
self.line = locationConverter.location(for: offset).line
|
|
||||||
self.index = index
|
|
||||||
self.expression = element.expression
|
|
||||||
}
|
|
||||||
|
|
||||||
var isClosure: Bool {
|
|
||||||
expression.is(ClosureExprSyntax.self)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 }
|
|
||||||
}
|
|
||||||
""")
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
""")
|
|
||||||
]
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,15 +0,0 @@
|
||||||
import SwiftIDEUtils
|
|
||||||
|
|
||||||
public extension SyntaxClassification {
|
|
||||||
// True if it is any kind of comment.
|
|
||||||
var isComment: Bool {
|
|
||||||
switch self {
|
|
||||||
case .lineComment, .docLineComment, .blockComment, .docBlockComment:
|
|
||||||
return true
|
|
||||||
case .none, .keyword, .identifier, .typeIdentifier, .operatorIdentifier, .dollarIdentifier, .integerLiteral,
|
|
||||||
.floatingLiteral, .stringLiteral, .stringInterpolationAnchor, .poundDirectiveKeyword, .buildConfigId,
|
|
||||||
.attribute, .objectLiteral, .editorPlaceholder, .regexLiteral:
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 ?? "")
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,2 +0,0 @@
|
||||||
@_spi(TestHelper)
|
|
||||||
public typealias ConfigurationRuleWrapper = (rule: Rule, initializedWithNonEmptyConfiguration: Bool)
|
|
|
@ -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)
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)"
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
]
|
|
|
@ -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]
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 }
|
|
||||||
}
|
|
|
@ -1,178 +0,0 @@
|
||||||
/// Type-erased protocol used to check whether a rule is collectable.
|
|
||||||
public protocol AnyCollectingRule: Rule { }
|
|
||||||
|
|
||||||
/// A rule that requires knowledge of all other files being linted.
|
|
||||||
public protocol CollectingRule: AnyCollectingRule {
|
|
||||||
/// The kind of information to collect for each file being linted for this rule.
|
|
||||||
associatedtype FileInfo
|
|
||||||
|
|
||||||
/// Collects information for the specified file, to be analyzed by a `CollectedLinter`.
|
|
||||||
///
|
|
||||||
/// - parameter file: The file for which to collect info.
|
|
||||||
/// - parameter compilerArguments: The compiler arguments needed to compile this file.
|
|
||||||
///
|
|
||||||
/// - returns: The collected file information.
|
|
||||||
func collectInfo(for file: SwiftLintFile, compilerArguments: [String]) -> FileInfo
|
|
||||||
|
|
||||||
/// Collects information for the specified file, to be analyzed by a `CollectedLinter`.
|
|
||||||
///
|
|
||||||
/// - parameter file: The file for which to collect info.
|
|
||||||
///
|
|
||||||
/// - returns: The collected file information.
|
|
||||||
func collectInfo(for file: SwiftLintFile) -> FileInfo
|
|
||||||
|
|
||||||
/// Executes the rule on a file after collecting file info for all files and returns any violations to the rule's
|
|
||||||
/// expectations.
|
|
||||||
///
|
|
||||||
/// - parameter file: The file for which to execute the rule.
|
|
||||||
/// - parameter collectedInfo: All collected info for all files.
|
|
||||||
/// - parameter compilerArguments: The compiler arguments needed to compile this file.
|
|
||||||
///
|
|
||||||
/// - returns: All style violations to the rule's expectations.
|
|
||||||
func validate(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo],
|
|
||||||
compilerArguments: [String]) -> [StyleViolation]
|
|
||||||
|
|
||||||
/// Executes the rule on a file after collecting file info for all files and returns any violations to the rule's
|
|
||||||
/// expectations.
|
|
||||||
///
|
|
||||||
/// - parameter file: The file for which to execute the rule.
|
|
||||||
/// - parameter collectedInfo: All collected info for all files.
|
|
||||||
///
|
|
||||||
/// - returns: All style violations to the rule's expectations.
|
|
||||||
func validate(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo]) -> [StyleViolation]
|
|
||||||
}
|
|
||||||
|
|
||||||
public extension CollectingRule {
|
|
||||||
func collectInfo(for file: SwiftLintFile, into storage: RuleStorage, compilerArguments: [String]) {
|
|
||||||
storage.collect(info: collectInfo(for: file, compilerArguments: compilerArguments),
|
|
||||||
for: file, in: self)
|
|
||||||
}
|
|
||||||
func validate(file: SwiftLintFile, using storage: RuleStorage, compilerArguments: [String]) -> [StyleViolation] {
|
|
||||||
guard let info = storage.collectedInfo(for: self) else {
|
|
||||||
queuedFatalError("Attempt to validate a CollectingRule before collecting info for it")
|
|
||||||
}
|
|
||||||
return validate(file: file, collectedInfo: info, compilerArguments: compilerArguments)
|
|
||||||
}
|
|
||||||
func collectInfo(for file: SwiftLintFile, compilerArguments: [String]) -> FileInfo {
|
|
||||||
return collectInfo(for: file)
|
|
||||||
}
|
|
||||||
func validate(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo],
|
|
||||||
compilerArguments: [String]) -> [StyleViolation] {
|
|
||||||
return validate(file: file, collectedInfo: collectedInfo)
|
|
||||||
}
|
|
||||||
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
|
||||||
queuedFatalError("Must call `validate(file:collectedInfo:)` for CollectingRule")
|
|
||||||
}
|
|
||||||
func validate(file: SwiftLintFile, compilerArguments: [String]) -> [StyleViolation] {
|
|
||||||
queuedFatalError("Must call `validate(file:collectedInfo:compilerArguments:)` for CollectingRule")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public extension CollectingRule where Self: AnalyzerRule {
|
|
||||||
func collectInfo(for file: SwiftLintFile) -> FileInfo {
|
|
||||||
queuedFatalError(
|
|
||||||
"Must call `collect(infoFor:compilerArguments:)` for AnalyzerRule & CollectingRule"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
func validate(file: SwiftLintFile) -> [StyleViolation] {
|
|
||||||
queuedFatalError(
|
|
||||||
"Must call `validate(file:collectedInfo:compilerArguments:)` for AnalyzerRule & CollectingRule"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
func validate(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo]) -> [StyleViolation] {
|
|
||||||
queuedFatalError(
|
|
||||||
"Must call `validate(file:collectedInfo:compilerArguments:)` for AnalyzerRule & CollectingRule"
|
|
||||||
)
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
/// A `CollectingRule` that is also a `CorrectableRule`.
|
|
||||||
@_spi(TestHelper)
|
|
||||||
public protocol CollectingCorrectableRule: CollectingRule, CorrectableRule {
|
|
||||||
/// Attempts to correct the violations to this rule in the specified file after collecting file info for all files
|
|
||||||
/// and returns all corrections that were applied.
|
|
||||||
///
|
|
||||||
/// - note: This function is called by the linter and is always implemented in extensions.
|
|
||||||
///
|
|
||||||
/// - parameter file: The file for which to execute the rule.
|
|
||||||
/// - parameter collectedInfo: All collected info.
|
|
||||||
/// - parameter compilerArguments: The compiler arguments needed to compile this file.
|
|
||||||
///
|
|
||||||
/// - returns: All corrections that were applied.
|
|
||||||
func correct(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo],
|
|
||||||
compilerArguments: [String]) -> [Correction]
|
|
||||||
|
|
||||||
/// Attempts to correct the violations to this rule in the specified file after collecting file info for all files
|
|
||||||
/// and returns all corrections that were applied.
|
|
||||||
///
|
|
||||||
/// - note: This function is called by the linter and is always implemented in extensions.
|
|
||||||
///
|
|
||||||
/// - parameter file: The file for which to execute the rule.
|
|
||||||
/// - parameter collectedInfo: All collected info.
|
|
||||||
///
|
|
||||||
/// - returns: All corrections that were applied.
|
|
||||||
func correct(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo]) -> [Correction]
|
|
||||||
}
|
|
||||||
|
|
||||||
@_spi(TestHelper)
|
|
||||||
public extension CollectingCorrectableRule {
|
|
||||||
func correct(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo],
|
|
||||||
compilerArguments: [String]) -> [Correction] {
|
|
||||||
return correct(file: file, collectedInfo: collectedInfo)
|
|
||||||
}
|
|
||||||
|
|
||||||
func correct(file: SwiftLintFile, using storage: RuleStorage, compilerArguments: [String]) -> [Correction] {
|
|
||||||
guard let info = storage.collectedInfo(for: self) else {
|
|
||||||
queuedFatalError("Attempt to correct a CollectingRule before collecting info for it")
|
|
||||||
}
|
|
||||||
return correct(file: file, collectedInfo: info, compilerArguments: compilerArguments)
|
|
||||||
}
|
|
||||||
|
|
||||||
func correct(file: SwiftLintFile) -> [Correction] {
|
|
||||||
queuedFatalError("Must call `correct(file:collectedInfo:)` for AnalyzerRule")
|
|
||||||
}
|
|
||||||
|
|
||||||
func correct(file: SwiftLintFile, compilerArguments: [String]) -> [Correction] {
|
|
||||||
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public extension CollectingCorrectableRule where Self: AnalyzerRule {
|
|
||||||
func correct(file: SwiftLintFile) -> [Correction] {
|
|
||||||
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
|
|
||||||
}
|
|
||||||
func correct(file: SwiftLintFile, compilerArguments: [String]) -> [Correction] {
|
|
||||||
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
|
|
||||||
}
|
|
||||||
func correct(file: SwiftLintFile, collectedInfo: [SwiftLintFile: FileInfo]) -> [Correction] {
|
|
||||||
queuedFatalError("Must call `correct(file:collectedInfo:compilerArguments:)` for AnalyzerRule")
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
public extension ConfigurationProviderRule {
|
|
||||||
init(configuration: Any) throws {
|
|
||||||
self.init()
|
|
||||||
try self.configuration.apply(configuration: configuration)
|
|
||||||
}
|
|
||||||
|
|
||||||
func isEqualTo(_ rule: Rule) -> Bool {
|
|
||||||
if let rule = rule as? Self {
|
|
||||||
return configuration.isEqualTo(rule.configuration)
|
|
||||||
}
|
|
||||||
return false
|
|
||||||
}
|
|
||||||
|
|
||||||
var configurationDescription: String {
|
|
||||||
return configuration.consoleDescription
|
|
||||||
}
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - == Implementations
|
|
||||||
|
|
||||||
/// :nodoc:
|
|
||||||
public extension Array where Element == Rule {
|
|
||||||
static func == (lhs: Array, rhs: Array) -> Bool {
|
|
||||||
if lhs.count != rhs.count { return false }
|
|
||||||
return !zip(lhs, rhs).contains { !$0.0.isEqualTo($0.1) }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/// Reports violations in a format that's both fun and easy to read.
|
|
||||||
public struct EmojiReporter: Reporter {
|
|
||||||
// MARK: - Reporter Conformance
|
|
||||||
|
|
||||||
public static let identifier = "emoji"
|
|
||||||
public static let isRealtime = false
|
|
||||||
public static let description = "Reports violations in the format that's both fun and easy to read."
|
|
||||||
|
|
||||||
public static func generateReport(_ violations: [StyleViolation]) -> String {
|
|
||||||
violations
|
|
||||||
.group { $0.location.file ?? "Other" }
|
|
||||||
.sorted { $0.key < $1.key }
|
|
||||||
.map(report)
|
|
||||||
.joined(separator: "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
private static func report(for file: String, with violations: [StyleViolation]) -> String {
|
|
||||||
let issueList = violations
|
|
||||||
.sorted { $0.severity == $1.severity ? $0.location > $1.location : $0.severity > $1.severity }
|
|
||||||
.map { violation in
|
|
||||||
let emoji = violation.severity == .error ? "⛔️" : "⚠️"
|
|
||||||
var lineString = ""
|
|
||||||
if let line = violation.location.line {
|
|
||||||
lineString = "Line \(line): "
|
|
||||||
}
|
|
||||||
return "\(emoji) \(lineString)\(violation.reason) (\(violation.ruleIdentifier))"
|
|
||||||
}
|
|
||||||
.joined(separator: "\n")
|
|
||||||
return """
|
|
||||||
\(file)
|
|
||||||
\(issueList)
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,168 +0,0 @@
|
||||||
import Foundation
|
|
||||||
|
|
||||||
private let formatter: DateFormatter = {
|
|
||||||
let formatter = DateFormatter()
|
|
||||||
formatter.dateStyle = .short
|
|
||||||
return formatter
|
|
||||||
}()
|
|
||||||
|
|
||||||
/// Reports violations as HTML.
|
|
||||||
public struct HTMLReporter: Reporter {
|
|
||||||
// MARK: - Reporter Conformance
|
|
||||||
|
|
||||||
public static let identifier = "html"
|
|
||||||
public static let isRealtime = false
|
|
||||||
public static let description = "Reports violations as HTML."
|
|
||||||
|
|
||||||
public static func generateReport(_ violations: [StyleViolation]) -> String {
|
|
||||||
return generateReport(violations, swiftlintVersion: Version.current.value,
|
|
||||||
dateString: formatter.string(from: Date()))
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Internal
|
|
||||||
|
|
||||||
// swiftlint:disable:next function_body_length
|
|
||||||
internal static func generateReport(_ violations: [StyleViolation], swiftlintVersion: String,
|
|
||||||
dateString: String) -> String {
|
|
||||||
let rows = violations.enumerated()
|
|
||||||
.map { generateSingleRow(for: $1, at: $0 + 1) }
|
|
||||||
.joined(separator: "\n")
|
|
||||||
|
|
||||||
let fileCount = Set(violations.compactMap({ $0.location.file })).count
|
|
||||||
let warningCount = violations.filter({ $0.severity == .warning }).count
|
|
||||||
let errorCount = violations.filter({ $0.severity == .error }).count
|
|
||||||
|
|
||||||
return """
|
|
||||||
<!doctype html>
|
|
||||||
<html>
|
|
||||||
\t<head>
|
|
||||||
\t\t<meta charset="utf-8" />
|
|
||||||
\t\t<meta name="viewport" content="width=device-width, initial-scale=1.0" />
|
|
||||||
\t\t
|
|
||||||
\t\t<style type="text/css">
|
|
||||||
\t\t\tbody {
|
|
||||||
\t\t\t\tfont-family: Arial, Helvetica, sans-serif;
|
|
||||||
\t\t\t\tfont-size: 0.9rem;
|
|
||||||
\t\t\t}
|
|
||||||
\t\t\t
|
|
||||||
\t\t\ttable {
|
|
||||||
\t\t\t\tborder: 1px solid gray;
|
|
||||||
\t\t\t\tborder-collapse: collapse;
|
|
||||||
\t\t\t\t-moz-box-shadow: 3px 3px 4px #AAA;
|
|
||||||
\t\t\t\t-webkit-box-shadow: 3px 3px 4px #AAA;
|
|
||||||
\t\t\t\tbox-shadow: 3px 3px 4px #AAA;
|
|
||||||
\t\t\t\tvertical-align: top;
|
|
||||||
\t\t\t\theight: 64px;
|
|
||||||
\t\t\t}
|
|
||||||
\t\t\t
|
|
||||||
\t\t\ttd, th {
|
|
||||||
\t\t\t\tborder: 1px solid #D3D3D3;
|
|
||||||
\t\t\t\tpadding: 5px 10px 5px 10px;
|
|
||||||
\t\t\t}
|
|
||||||
\t\t\t
|
|
||||||
\t\t\tth {
|
|
||||||
\t\t\t\tborder-bottom: 1px solid gray;
|
|
||||||
\t\t\t\tbackground-color: rgba(41,52,92,0.313);
|
|
||||||
\t\t\t}
|
|
||||||
\t\t\t
|
|
||||||
\t\t\t.error, .warning {
|
|
||||||
\t\t\t\ttext-align: center;
|
|
||||||
\t\t\t}
|
|
||||||
\t\t\t
|
|
||||||
\t\t\t.error {
|
|
||||||
\t\t\t\tbackground-color: #FF9D92;
|
|
||||||
\t\t\t\tcolor: #7F0800;
|
|
||||||
\t\t\t}
|
|
||||||
\t\t\t
|
|
||||||
\t\t\t.warning {
|
|
||||||
\t\t\t\tbackground-color: #FFF59E;
|
|
||||||
\t\t\t\tcolor: #7F7000;
|
|
||||||
\t\t\t}
|
|
||||||
\t\t</style>
|
|
||||||
\t\t
|
|
||||||
\t\t<title>SwiftLint Report</title>
|
|
||||||
\t</head>
|
|
||||||
\t<body>
|
|
||||||
\t\t<h1>SwiftLint Report</h1>
|
|
||||||
\t\t
|
|
||||||
\t\t<hr />
|
|
||||||
\t\t
|
|
||||||
\t\t<h2>Violations</h2>
|
|
||||||
\t\t
|
|
||||||
\t\t<table>
|
|
||||||
\t\t\t<thead>
|
|
||||||
\t\t\t\t<tr>
|
|
||||||
\t\t\t\t\t<th style="width: 60pt;">
|
|
||||||
\t\t\t\t\t\t<b>Serial No.</b>
|
|
||||||
\t\t\t\t\t</th>
|
|
||||||
\t\t\t\t\t<th style="width: 500pt;">
|
|
||||||
\t\t\t\t\t\t<b>File</b>
|
|
||||||
\t\t\t\t\t</th>
|
|
||||||
\t\t\t\t\t<th style="width: 60pt;">
|
|
||||||
\t\t\t\t\t\t<b>Location</b>
|
|
||||||
\t\t\t\t\t</th>
|
|
||||||
\t\t\t\t\t<th style="width: 60pt;">
|
|
||||||
\t\t\t\t\t\t<b>Severity</b>
|
|
||||||
\t\t\t\t\t</th>
|
|
||||||
\t\t\t\t\t<th style="width: 500pt;">
|
|
||||||
\t\t\t\t\t\t<b>Message</b>
|
|
||||||
\t\t\t\t\t</th>
|
|
||||||
\t\t\t\t</tr>
|
|
||||||
\t\t\t</thead>
|
|
||||||
\t\t\t<tbody>
|
|
||||||
\(rows)
|
|
||||||
\t\t\t</tbody>
|
|
||||||
\t\t</table>
|
|
||||||
\t\t
|
|
||||||
\t\t<br/>
|
|
||||||
\t\t
|
|
||||||
\t\t<h2>Summary</h2>
|
|
||||||
\t\t
|
|
||||||
\t\t<table>
|
|
||||||
\t\t\t<tbody>
|
|
||||||
\t\t\t\t<tr>
|
|
||||||
\t\t\t\t\t<td>Total files with violations</td>
|
|
||||||
\t\t\t\t\t<td>\(fileCount)</td>
|
|
||||||
\t\t\t\t</tr>
|
|
||||||
\t\t\t\t<tr>
|
|
||||||
\t\t\t\t\t<td>Total warnings</td>
|
|
||||||
\t\t\t\t\t<td>\(warningCount)</td>
|
|
||||||
\t\t\t\t</tr>
|
|
||||||
\t\t\t\t<tr>
|
|
||||||
\t\t\t\t\t<td>Total errors</td>
|
|
||||||
\t\t\t\t\t<td>\(errorCount)</td>
|
|
||||||
\t\t\t\t</tr>
|
|
||||||
\t\t\t</tbody>
|
|
||||||
\t\t</table>
|
|
||||||
\t\t
|
|
||||||
\t\t<hr />
|
|
||||||
\t\t
|
|
||||||
\t\t<p>
|
|
||||||
\t\t\tCreated with
|
|
||||||
\t\t\t<a href="https://github.com/realm/SwiftLint"><b>SwiftLint</b></a>
|
|
||||||
\t\t\t\(swiftlintVersion) on \(dateString)
|
|
||||||
\t\t</p>
|
|
||||||
\t</body>
|
|
||||||
</html>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: - Private
|
|
||||||
|
|
||||||
private static func generateSingleRow(for violation: StyleViolation, at index: Int) -> String {
|
|
||||||
let severity: String = violation.severity.rawValue.capitalized
|
|
||||||
let location = violation.location
|
|
||||||
let file: String = (violation.location.relativeFile ?? "<nopath>").escapedForXML()
|
|
||||||
let line: Int = location.line ?? 0
|
|
||||||
let character: Int = location.character ?? 0
|
|
||||||
return """
|
|
||||||
\t\t\t\t<tr>
|
|
||||||
\t\t\t\t\t<td style="text-align: right;">\(index)</td>
|
|
||||||
\t\t\t\t\t<td>\(file)</td>
|
|
||||||
\t\t\t\t\t<td style="text-align: center;">\(line):\(character)</td>
|
|
||||||
\t\t\t\t\t<td class="\(severity.lowercased())">\(severity)</td>
|
|
||||||
\t\t\t\t\t<td>\(violation.reason.escapedForXML())</td>
|
|
||||||
\t\t\t\t</tr>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,36 +0,0 @@
|
||||||
/// Reports violations as JUnit XML.
|
|
||||||
public struct JUnitReporter: Reporter {
|
|
||||||
// MARK: - Reporter Conformance
|
|
||||||
|
|
||||||
public static let identifier = "junit"
|
|
||||||
public static let isRealtime = false
|
|
||||||
public static let description = "Reports violations as JUnit XML."
|
|
||||||
|
|
||||||
public static func generateReport(_ violations: [StyleViolation]) -> String {
|
|
||||||
let warningCount = violations.filter({ $0.severity == .warning }).count
|
|
||||||
let errorCount = violations.filter({ $0.severity == .error }).count
|
|
||||||
|
|
||||||
return """
|
|
||||||
<?xml version="1.0" encoding="utf-8"?>
|
|
||||||
<testsuites failures="\(warningCount)" errors="\(errorCount)">
|
|
||||||
\t<testsuite failures="\(warningCount)" errors="\(errorCount)">
|
|
||||||
\(violations.map(testCase(for:)).joined(separator: "\n"))
|
|
||||||
\t</testsuite>
|
|
||||||
</testsuites>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
private static func testCase(for violation: StyleViolation) -> String {
|
|
||||||
let fileName = (violation.location.file ?? "<nopath>").escapedForXML()
|
|
||||||
let reason = violation.reason.escapedForXML()
|
|
||||||
let severity = violation.severity.rawValue.capitalized
|
|
||||||
let lineNumber = String(violation.location.line ?? 0)
|
|
||||||
let message = severity + ":" + "Line:" + lineNumber
|
|
||||||
|
|
||||||
return """
|
|
||||||
\t\t<testcase classname='Formatting Test' name='\(fileName)'>
|
|
||||||
\t\t\t<failure message='\(reason)'>\(message)</failure>
|
|
||||||
\t\t</testcase>
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,31 +0,0 @@
|
||||||
/// Reports violations with relative paths.
|
|
||||||
public struct RelativePathReporter: Reporter {
|
|
||||||
// MARK: - Reporter Conformance
|
|
||||||
|
|
||||||
public static let identifier = "relative-path"
|
|
||||||
public static let isRealtime = true
|
|
||||||
public static let description = "Reports violations with relative paths."
|
|
||||||
|
|
||||||
public static func generateReport(_ violations: [StyleViolation]) -> String {
|
|
||||||
return violations.map(generateForSingleViolation).joined(separator: "\n")
|
|
||||||
}
|
|
||||||
|
|
||||||
/// Generates a report for a single violation.
|
|
||||||
///
|
|
||||||
/// - parameter violation: The violation to report.
|
|
||||||
///
|
|
||||||
/// - returns: The report for a single violation.
|
|
||||||
internal static func generateForSingleViolation(_ violation: StyleViolation) -> String {
|
|
||||||
// {relative_path_to_file}{:line}{:character}: {error,warning}: {content}
|
|
||||||
|
|
||||||
return [
|
|
||||||
"\(violation.location.relativeFile ?? "<nopath>")",
|
|
||||||
":\(violation.location.line ?? 1)",
|
|
||||||
":\(violation.location.character ?? 1): ",
|
|
||||||
"\(violation.severity.rawValue): ",
|
|
||||||
"\(violation.ruleName) Violation: ",
|
|
||||||
violation.reason,
|
|
||||||
" (\(violation.ruleIdentifier))"
|
|
||||||
].joined()
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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)) ?? ""
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,5 +0,0 @@
|
||||||
/// The rule list containing all available rules built into SwiftLintCore.
|
|
||||||
public let coreRules: [Rule.Type] = [
|
|
||||||
CustomRules.self,
|
|
||||||
SuperfluousDisableCommandRule.self
|
|
||||||
]
|
|
|
@ -1,32 +0,0 @@
|
||||||
@_spi(TestHelper)
|
|
||||||
public struct SuperfluousDisableCommandRule: ConfigurationProviderRule, SourceKitFreeRule {
|
|
||||||
public var configuration = SeverityConfiguration<Self>(.warning)
|
|
||||||
|
|
||||||
public init() {}
|
|
||||||
|
|
||||||
public static let description = RuleDescription(
|
|
||||||
identifier: "superfluous_disable_command",
|
|
||||||
name: "Superfluous Disable Command",
|
|
||||||
description: """
|
|
||||||
SwiftLint 'disable' commands are superfluous when the disabled rule would not have triggered a violation \
|
|
||||||
in the disabled region. Use " - " if you wish to document a command.
|
|
||||||
""",
|
|
||||||
kind: .lint
|
|
||||||
)
|
|
||||||
|
|
||||||
public func validate(file: SwiftLintFile) -> [StyleViolation] {
|
|
||||||
// This rule is implemented in Linter.swift
|
|
||||||
return []
|
|
||||||
}
|
|
||||||
|
|
||||||
func reason(for rule: Rule.Type) -> String {
|
|
||||||
"""
|
|
||||||
SwiftLint rule '\(rule.description.identifier)' did not trigger a violation in the disabled region; \
|
|
||||||
remove the disable command
|
|
||||||
"""
|
|
||||||
}
|
|
||||||
|
|
||||||
func reason(forNonExistentRule rule: String) -> String {
|
|
||||||
return "'\(rule)' is not a valid SwiftLint rule; remove it from the disable command"
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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([])
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -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 } }
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1 +0,0 @@
|
||||||
@_exported import SwiftLintCore
|
|
|
@ -34,40 +34,31 @@ public struct RuleListDocumentation {
|
||||||
|
|
||||||
private var indexContents: String {
|
private var indexContents: String {
|
||||||
let defaultRuleDocumentations = ruleDocumentations.filter { !$0.isOptInRule }
|
let defaultRuleDocumentations = ruleDocumentations.filter { !$0.isOptInRule }
|
||||||
let optInRuleDocumentations = ruleDocumentations.filter { $0.isOptInRule && !$0.isAnalyzerRule }
|
let optInRuleDocumentations = ruleDocumentations.filter { $0.isOptInRule }
|
||||||
let analyzerRuleDocumentations = ruleDocumentations.filter { $0.isAnalyzerRule }
|
|
||||||
|
|
||||||
return """
|
return """
|
||||||
# Rule Directory
|
# Rule Directory
|
||||||
|
|
||||||
## Default Rules
|
## Default Rules
|
||||||
|
|
||||||
\(defaultRuleDocumentations.map(makeListEntry).joined(separator: "\n"))
|
\(defaultRuleDocumentations
|
||||||
|
.map { "* `\($0.ruleIdentifier)`: \($0.ruleName)" }
|
||||||
|
.joined(separator: "\n"))
|
||||||
|
|
||||||
## Opt-in Rules
|
## Opt-In Rules
|
||||||
|
|
||||||
\(optInRuleDocumentations.map(makeListEntry).joined(separator: "\n"))
|
\(optInRuleDocumentations
|
||||||
|
.map { "* `\($0.ruleIdentifier)`: \($0.ruleName)" }
|
||||||
## Analyzer Rules
|
.joined(separator: "\n"))
|
||||||
|
|
||||||
\(analyzerRuleDocumentations.map(makeListEntry).joined(separator: "\n"))
|
|
||||||
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
||||||
|
|
||||||
private func makeListEntry(from rule: RuleDocumentation) -> String {
|
|
||||||
"* [`\(rule.ruleIdentifier)`](\(rule.ruleIdentifier).md): \(rule.ruleName)"
|
|
||||||
}
|
|
||||||
|
|
||||||
private var swiftSyntaxDashboardContents: String {
|
private var swiftSyntaxDashboardContents: String {
|
||||||
let linterRuleDocumentations = ruleDocumentations.filter(\.isLinterRule)
|
let linterRuleDocumentations = ruleDocumentations.filter(\.isLinterRule)
|
||||||
let rulesUsingSourceKit = linterRuleDocumentations.filter(\.usesSourceKit)
|
let rulesUsingSourceKit = linterRuleDocumentations.filter(\.usesSourceKit)
|
||||||
let rulesNotUsingSourceKit = linterRuleDocumentations.filter { !$0.usesSourceKit }
|
let rulesNotUsingSourceKit = linterRuleDocumentations.filter { !$0.usesSourceKit }
|
||||||
let percentUsingSourceKit = Int(rulesUsingSourceKit.count * 100 / linterRuleDocumentations.count)
|
let percentUsingSourceKit = Int(rulesUsingSourceKit.count * 100 / linterRuleDocumentations.count)
|
||||||
let enabledSourceKitRules = rulesUsingSourceKit.filter(\.isEnabledByDefault)
|
|
||||||
let disabledSourceKitRules = rulesUsingSourceKit.filter(\.isDisabledByDefault)
|
|
||||||
let enabledSourceKitFreeRules = rulesNotUsingSourceKit.filter(\.isEnabledByDefault)
|
|
||||||
let disabledSourceKitFreeRules = rulesNotUsingSourceKit.filter(\.isDisabledByDefault)
|
|
||||||
|
|
||||||
return """
|
return """
|
||||||
# Swift Syntax Dashboard
|
# Swift Syntax Dashboard
|
||||||
|
@ -82,23 +73,35 @@ public struct RuleListDocumentation {
|
||||||
|
|
||||||
## Rules Using SourceKit
|
## Rules Using SourceKit
|
||||||
|
|
||||||
### Default Rules (\(enabledSourceKitRules.count))
|
### Enabled By Default (\(rulesUsingSourceKit.filter(\.isEnabledByDefault).count))
|
||||||
|
|
||||||
\(enabledSourceKitRules.map(makeListEntry).joined(separator: "\n"))
|
\(rulesUsingSourceKit
|
||||||
|
.filter(\.isEnabledByDefault)
|
||||||
|
.map { "* `\($0.ruleIdentifier)`: \($0.ruleName)" }
|
||||||
|
.joined(separator: "\n"))
|
||||||
|
|
||||||
### Opt-in Rules (\(disabledSourceKitRules.count))
|
### Opt-In (\(rulesUsingSourceKit.filter(\.isDisabledByDefault).count))
|
||||||
|
|
||||||
\(disabledSourceKitRules.map(makeListEntry).joined(separator: "\n"))
|
\(rulesUsingSourceKit
|
||||||
|
.filter(\.isDisabledByDefault)
|
||||||
|
.map { "* `\($0.ruleIdentifier)`: \($0.ruleName)" }
|
||||||
|
.joined(separator: "\n"))
|
||||||
|
|
||||||
## Rules not Using SourceKit
|
## Rules Not Using SourceKit
|
||||||
|
|
||||||
### Default Rules (\(enabledSourceKitFreeRules.count))
|
### Enabled By Default (\(rulesNotUsingSourceKit.filter(\.isEnabledByDefault).count))
|
||||||
|
|
||||||
\(enabledSourceKitFreeRules.map(makeListEntry).joined(separator: "\n"))
|
\(rulesNotUsingSourceKit
|
||||||
|
.filter(\.isEnabledByDefault)
|
||||||
|
.map { "* `\($0.ruleIdentifier)`: \($0.ruleName)" }
|
||||||
|
.joined(separator: "\n"))
|
||||||
|
|
||||||
### Opt-in Rules (\(disabledSourceKitFreeRules.count))
|
### Opt-In (\(rulesNotUsingSourceKit.filter(\.isDisabledByDefault).count))
|
||||||
|
|
||||||
\(disabledSourceKitFreeRules.map(makeListEntry).joined(separator: "\n"))
|
\(rulesNotUsingSourceKit
|
||||||
|
.filter(\.isDisabledByDefault)
|
||||||
|
.map { "* `\($0.ruleIdentifier)`: \($0.ruleName)" }
|
||||||
|
.joined(separator: "\n"))
|
||||||
|
|
||||||
"""
|
"""
|
||||||
}
|
}
|
|
@ -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
|
|
||||||
}
|
|
||||||
}
|
|
|
@ -1,6 +1,6 @@
|
||||||
import Dispatch
|
import Dispatch
|
||||||
|
|
||||||
public extension Array where Element: Equatable {
|
extension Array where Element: Equatable {
|
||||||
/// The elements in this array, discarding duplicates after the first one.
|
/// The elements in this array, discarding duplicates after the first one.
|
||||||
/// Order-preserving.
|
/// Order-preserving.
|
||||||
var unique: [Element] {
|
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.
|
/// Produces an array containing the passed `obj` value.
|
||||||
/// If `obj` is an array already, return it.
|
/// If `obj` is an array already, return it.
|
||||||
/// If `obj` is a set, copy its elements to a new array.
|
/// 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.
|
/// Produces an array containing the passed `obj` value.
|
||||||
/// If `obj` is an array already, return it.
|
/// If `obj` is an array already, return it.
|
||||||
/// If `obj` is a value of type `Element`, return a single-item array containing it.
|
/// If `obj` is a value of type `Element`, return a single-item array containing it.
|
||||||
|
@ -108,7 +108,7 @@ public extension Array {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
public extension Collection {
|
extension Collection {
|
||||||
/// Whether this collection has one or more element.
|
/// Whether this collection has one or more element.
|
||||||
var isNotEmpty: Bool {
|
var isNotEmpty: Bool {
|
||||||
return !isEmpty
|
return !isEmpty
|
|
@ -1,7 +1,7 @@
|
||||||
import SourceKittenFramework
|
import SourceKittenFramework
|
||||||
import SwiftSyntax
|
import SwiftSyntax
|
||||||
|
|
||||||
public extension ByteCount {
|
extension ByteCount {
|
||||||
/// Converts a SwiftSyntax `AbsolutePosition` to a SourceKitten `ByteCount`.
|
/// Converts a SwiftSyntax `AbsolutePosition` to a SourceKitten `ByteCount`.
|
||||||
///
|
///
|
||||||
/// - parameter position: The SwiftSyntax position to convert.
|
/// - parameter position: The SwiftSyntax position to convert.
|
|
@ -1,4 +1,3 @@
|
||||||
// swiftlint:disable:next blanket_disable_command
|
|
||||||
// swiftlint:disable all
|
// swiftlint:disable all
|
||||||
// Copied from https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Windows.swift
|
// Copied from https://github.com/apple/swift-algorithms/blob/main/Sources/Algorithms/Windows.swift
|
||||||
|
|
|
@ -47,15 +47,15 @@ extension Configuration {
|
||||||
Self.nestedConfigIsSelfByIdentifierLock.unlock()
|
Self.nestedConfigIsSelfByIdentifierLock.unlock()
|
||||||
}
|
}
|
||||||
|
|
||||||
internal static func getIsNestedConfigurationSelf(forIdentifier identifier: String) -> Bool {
|
internal static func getIsNestedConfigurationSelf(forIdentifier identifier: String) -> Bool? {
|
||||||
Self.nestedConfigIsSelfByIdentifierLock.lock()
|
Self.nestedConfigIsSelfByIdentifierLock.lock()
|
||||||
defer { Self.nestedConfigIsSelfByIdentifierLock.unlock() }
|
defer { Self.nestedConfigIsSelfByIdentifierLock.unlock() }
|
||||||
return Self.nestedConfigIsSelfByIdentifier[identifier] ?? false
|
return Self.nestedConfigIsSelfByIdentifier[identifier]
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: SwiftLint Cache (On-Disk)
|
// MARK: SwiftLint Cache (On-Disk)
|
||||||
internal var cacheDescription: String {
|
internal var cacheDescription: String {
|
||||||
if let computedCacheDescription {
|
if let computedCacheDescription = computedCacheDescription {
|
||||||
return computedCacheDescription
|
return computedCacheDescription
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -92,7 +92,7 @@ extension Configuration {
|
||||||
do {
|
do {
|
||||||
try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
|
try FileManager.default.createDirectory(at: folder, withIntermediateDirectories: true, attributes: nil)
|
||||||
} catch {
|
} catch {
|
||||||
Issue.genericWarning("Cannot create cache: " + error.localizedDescription).print()
|
queuedPrintError("Error while creating cache: " + error.localizedDescription)
|
||||||
}
|
}
|
||||||
|
|
||||||
return folder
|
return folder
|
|
@ -1,7 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
@_spi(TestHelper)
|
internal extension Configuration {
|
||||||
public extension Configuration {
|
|
||||||
struct FileGraph: Hashable {
|
struct FileGraph: Hashable {
|
||||||
// MARK: - Properties
|
// MARK: - Properties
|
||||||
private static let defaultRemoteConfigTimeout: TimeInterval = 2
|
private static let defaultRemoteConfigTimeout: TimeInterval = 2
|
||||||
|
@ -11,7 +10,7 @@ public extension Configuration {
|
||||||
|
|
||||||
private let ignoreParentAndChildConfigs: Bool
|
private let ignoreParentAndChildConfigs: Bool
|
||||||
|
|
||||||
private var vertices: Set<Vertex>
|
private var vertices: Set<Vertix>
|
||||||
private var edges: Set<Edge>
|
private var edges: Set<Edge>
|
||||||
|
|
||||||
private var isBuilt = false
|
private var isBuilt = false
|
||||||
|
@ -19,7 +18,7 @@ public extension Configuration {
|
||||||
// MARK: - Initializers
|
// MARK: - Initializers
|
||||||
internal init(commandLineChildConfigs: [String], rootDirectory: String, ignoreParentAndChildConfigs: Bool) {
|
internal init(commandLineChildConfigs: [String], rootDirectory: String, ignoreParentAndChildConfigs: Bool) {
|
||||||
let verticesArray = commandLineChildConfigs.map { config in
|
let verticesArray = commandLineChildConfigs.map { config in
|
||||||
Vertex(string: config, rootDirectory: rootDirectory, isInitialVertex: true)
|
Vertix(string: config, rootDirectory: rootDirectory, isInitialVertix: true)
|
||||||
}
|
}
|
||||||
vertices = Set(verticesArray)
|
vertices = Set(verticesArray)
|
||||||
edges = Set(zip(verticesArray, verticesArray.dropFirst()).map { Edge(parent: $0.0, child: $0.1) })
|
edges = Set(zip(verticesArray, verticesArray.dropFirst()).map { Edge(parent: $0.0, child: $0.1) })
|
||||||
|
@ -28,6 +27,14 @@ public extension Configuration {
|
||||||
self.ignoreParentAndChildConfigs = ignoreParentAndChildConfigs
|
self.ignoreParentAndChildConfigs = ignoreParentAndChildConfigs
|
||||||
}
|
}
|
||||||
|
|
||||||
|
internal init(config: String, rootDirectory: String, ignoreParentAndChildConfigs: Bool) throws {
|
||||||
|
self.init(
|
||||||
|
commandLineChildConfigs: [config],
|
||||||
|
rootDirectory: rootDirectory,
|
||||||
|
ignoreParentAndChildConfigs: ignoreParentAndChildConfigs
|
||||||
|
)
|
||||||
|
}
|
||||||
|
|
||||||
/// Dummy init to get a FileGraph that just represents a root directory
|
/// Dummy init to get a FileGraph that just represents a root directory
|
||||||
internal init(rootDirectory: String) {
|
internal init(rootDirectory: String) {
|
||||||
self.init(
|
self.init(
|
||||||
|
@ -56,11 +63,11 @@ public extension Configuration {
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func includesFile(atPath path: String) -> Bool {
|
internal func includesFile(atPath path: String) -> Bool? {
|
||||||
guard isBuilt else { return false }
|
guard isBuilt else { return nil }
|
||||||
|
|
||||||
return vertices.contains { vertex in
|
return vertices.contains { vertix in
|
||||||
if case let .existing(filePath) = vertex.filePath {
|
if case let .existing(filePath) = vertix.filePath {
|
||||||
return path == filePath
|
return path == filePath
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -70,19 +77,19 @@ public extension Configuration {
|
||||||
|
|
||||||
// MARK: Building
|
// MARK: Building
|
||||||
private mutating func build() throws {
|
private mutating func build() throws {
|
||||||
for vertex in vertices {
|
for vertix in vertices {
|
||||||
try process(vertex: vertex)
|
try process(vertix: vertix)
|
||||||
}
|
}
|
||||||
|
|
||||||
isBuilt = true
|
isBuilt = true
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func process(
|
private mutating func process(
|
||||||
vertex: Vertex,
|
vertix: Vertix,
|
||||||
remoteConfigTimeoutOverride: TimeInterval? = nil,
|
remoteConfigTimeoutOverride: TimeInterval? = nil,
|
||||||
remoteConfigTimeoutIfCachedOverride: TimeInterval? = nil
|
remoteConfigTimeoutIfCachedOverride: TimeInterval? = nil
|
||||||
) throws {
|
) throws {
|
||||||
try vertex.build(
|
try vertix.build(
|
||||||
remoteConfigTimeout: remoteConfigTimeoutOverride ?? Configuration.FileGraph.defaultRemoteConfigTimeout,
|
remoteConfigTimeout: remoteConfigTimeoutOverride ?? Configuration.FileGraph.defaultRemoteConfigTimeout,
|
||||||
remoteConfigTimeoutIfCached: remoteConfigTimeoutIfCachedOverride
|
remoteConfigTimeoutIfCached: remoteConfigTimeoutIfCachedOverride
|
||||||
?? remoteConfigTimeoutOverride ?? Configuration.FileGraph.defaultRemoteConfigTimeoutIfCached
|
?? remoteConfigTimeoutOverride ?? Configuration.FileGraph.defaultRemoteConfigTimeoutIfCached
|
||||||
|
@ -91,13 +98,13 @@ public extension Configuration {
|
||||||
if !ignoreParentAndChildConfigs {
|
if !ignoreParentAndChildConfigs {
|
||||||
try processPossibleReference(
|
try processPossibleReference(
|
||||||
ofType: .childConfig,
|
ofType: .childConfig,
|
||||||
from: vertex,
|
from: vertix,
|
||||||
remoteConfigTimeoutOverride: remoteConfigTimeoutOverride,
|
remoteConfigTimeoutOverride: remoteConfigTimeoutOverride,
|
||||||
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCachedOverride
|
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCachedOverride
|
||||||
)
|
)
|
||||||
try processPossibleReference(
|
try processPossibleReference(
|
||||||
ofType: .parentConfig,
|
ofType: .parentConfig,
|
||||||
from: vertex,
|
from: vertix,
|
||||||
remoteConfigTimeoutOverride: remoteConfigTimeoutOverride,
|
remoteConfigTimeoutOverride: remoteConfigTimeoutOverride,
|
||||||
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCachedOverride
|
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCachedOverride
|
||||||
)
|
)
|
||||||
|
@ -106,46 +113,46 @@ public extension Configuration {
|
||||||
|
|
||||||
private mutating func processPossibleReference(
|
private mutating func processPossibleReference(
|
||||||
ofType type: EdgeType,
|
ofType type: EdgeType,
|
||||||
from vertex: Vertex,
|
from vertix: Vertix,
|
||||||
remoteConfigTimeoutOverride: TimeInterval?,
|
remoteConfigTimeoutOverride: TimeInterval?,
|
||||||
remoteConfigTimeoutIfCachedOverride: TimeInterval?
|
remoteConfigTimeoutIfCachedOverride: TimeInterval?
|
||||||
) throws {
|
) throws {
|
||||||
let key = type == .childConfig ? Configuration.Key.childConfig.rawValue
|
let key = type == .childConfig ? Configuration.Key.childConfig.rawValue
|
||||||
: Configuration.Key.parentConfig.rawValue
|
: Configuration.Key.parentConfig.rawValue
|
||||||
|
|
||||||
if let reference = vertex.configurationDict[key] as? String {
|
if let reference = vertix.configurationDict[key] as? String {
|
||||||
let referencedVertex = Vertex(string: reference, rootDirectory: vertex.rootDirectory,
|
let referencedVertix = Vertix(string: reference, rootDirectory: vertix.rootDirectory,
|
||||||
isInitialVertex: false)
|
isInitialVertix: false)
|
||||||
|
|
||||||
// Local vertices are allowed to have local / remote references
|
// Local vertices are allowed to have local / remote references
|
||||||
// Remote vertices are only allowed to have remote references
|
// Remote vertices are only allowed to have remote references
|
||||||
if vertex.originatesFromRemote && !referencedVertex.originatesFromRemote {
|
if vertix.originatesFromRemote && !referencedVertix.originatesFromRemote {
|
||||||
throw Issue.genericWarning("Remote configs are not allowed to reference local configs.")
|
throw ConfigurationError.generic("Remote configs are not allowed to reference local configs.")
|
||||||
} else {
|
} else {
|
||||||
let existingVertex = findPossiblyExistingVertex(sameAs: referencedVertex)
|
let existingVertix = findPossiblyExistingVertix(sameAs: referencedVertix)
|
||||||
let existingVertexCopy = existingVertex.map { $0.copy(withNewRootDirectory: rootDirectory) }
|
let existingVertixCopy = existingVertix.map { $0.copy(withNewRootDirectory: rootDirectory) }
|
||||||
|
|
||||||
edges.insert(
|
edges.insert(
|
||||||
type == .childConfig
|
type == .childConfig
|
||||||
? Edge(parent: vertex, child: existingVertexCopy ?? referencedVertex)
|
? Edge(parent: vertix, child: existingVertixCopy ?? referencedVertix)
|
||||||
: Edge(parent: existingVertexCopy ?? referencedVertex, child: vertex)
|
: Edge(parent: existingVertixCopy ?? referencedVertix, child: vertix)
|
||||||
)
|
)
|
||||||
|
|
||||||
if existingVertex == nil {
|
if existingVertix == nil {
|
||||||
vertices.insert(referencedVertex)
|
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 =
|
let remoteConfigTimeout =
|
||||||
vertex.configurationDict[Configuration.Key.remoteConfigTimeout.rawValue]
|
vertix.configurationDict[Configuration.Key.remoteConfigTimeout.rawValue]
|
||||||
as? TimeInterval
|
as? TimeInterval
|
||||||
?? remoteConfigTimeoutOverride // from vertex parent
|
?? remoteConfigTimeoutOverride // from vertix parent
|
||||||
let remoteConfigTimeoutIfCached =
|
let remoteConfigTimeoutIfCached =
|
||||||
vertex.configurationDict[Configuration.Key.remoteConfigTimeoutIfCached.rawValue]
|
vertix.configurationDict[Configuration.Key.remoteConfigTimeoutIfCached.rawValue]
|
||||||
as? TimeInterval
|
as? TimeInterval
|
||||||
?? remoteConfigTimeoutIfCachedOverride // from vertex parent
|
?? remoteConfigTimeoutIfCachedOverride // from vertix parent
|
||||||
|
|
||||||
try process(
|
try process(
|
||||||
vertex: referencedVertex,
|
vertix: referencedVertix,
|
||||||
remoteConfigTimeoutOverride: remoteConfigTimeout,
|
remoteConfigTimeoutOverride: remoteConfigTimeout,
|
||||||
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCached
|
remoteConfigTimeoutIfCachedOverride: remoteConfigTimeoutIfCached
|
||||||
)
|
)
|
||||||
|
@ -154,10 +161,10 @@ public extension Configuration {
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private func findPossiblyExistingVertex(sameAs vertex: Vertex) -> Vertex? {
|
private func findPossiblyExistingVertix(sameAs vertix: Vertix) -> Vertix? {
|
||||||
return vertices.first {
|
return vertices.first {
|
||||||
$0.originalRemoteString != nil && $0.originalRemoteString == vertex.originalRemoteString
|
$0.originalRemoteString != nil && $0.originalRemoteString == vertix.originalRemoteString
|
||||||
} ?? vertices.first { $0.filePath == vertex.filePath }
|
} ?? vertices.first { $0.filePath == vertix.filePath }
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: Validating
|
// MARK: Validating
|
||||||
|
@ -165,11 +172,11 @@ public extension Configuration {
|
||||||
/// If successful, returns array of configuration dicts that represents the graph
|
/// If successful, returns array of configuration dicts that represents the graph
|
||||||
private func validate() throws -> [(configurationDict: [String: Any], rootDirectory: String)] {
|
private func validate() throws -> [(configurationDict: [String: Any], rootDirectory: String)] {
|
||||||
// Detect cycles via back-edge detection during DFS
|
// 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
|
// Please note that the equality check (`==`), not the identity check (`===`) is used
|
||||||
let children = edges.filter { $0.parent == stack.last }.map { $0.child! }
|
let children = edges.filter { $0.parent == stack.last }.map { $0.child! }
|
||||||
if stack.contains(where: children.contains) {
|
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 "
|
+ "Please check the hierarchy of configuration files passed via the command line "
|
||||||
+ "and the childConfig / parentConfig entries within them.")
|
+ "and the childConfig / parentConfig entries within them.")
|
||||||
}
|
}
|
||||||
|
@ -180,36 +187,36 @@ public extension Configuration {
|
||||||
|
|
||||||
// Detect ambiguities
|
// Detect ambiguities
|
||||||
if (edges.contains { edge in edges.filter { $0.parent == edge.parent }.count > 1 }) {
|
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, "
|
+ "More than one parent is declared for a specific configuration, "
|
||||||
+ "where there should only be exactly one.")
|
+ "where there should only be exactly one.")
|
||||||
}
|
}
|
||||||
|
|
||||||
if (edges.contains { edge in edges.filter { $0.child == edge.child }.count > 1 }) {
|
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, "
|
+ "More than one child is declared for a specific configuration, "
|
||||||
+ "where there should only be exactly one.")
|
+ "where there should only be exactly one.")
|
||||||
}
|
}
|
||||||
|
|
||||||
// The graph should be like an array if validation passed -> return that array
|
// The graph should be like an array if validation passed -> return that array
|
||||||
guard
|
guard
|
||||||
let startingVertex = (vertices.first { vertex in !edges.contains { $0.child == vertex } })
|
let startingVertix = (vertices.first { vertix in !edges.contains { $0.child == vertix } })
|
||||||
else {
|
else {
|
||||||
guard vertices.isEmpty else {
|
guard vertices.isEmpty else {
|
||||||
throw Issue.genericWarning("Unknown Configuration Error")
|
throw ConfigurationError.generic("Unknown Configuration Error")
|
||||||
}
|
}
|
||||||
|
|
||||||
return []
|
return []
|
||||||
}
|
}
|
||||||
|
|
||||||
var verticesToMerge = [startingVertex]
|
var verticesToMerge = [startingVertix]
|
||||||
while let vertex = (edges.first { $0.parent == verticesToMerge.last }?.child) {
|
while let vertix = (edges.first { $0.parent == verticesToMerge.last }?.child) {
|
||||||
guard !verticesToMerge.contains(vertex) else {
|
guard !verticesToMerge.contains(vertix) else {
|
||||||
// This shouldn't happen on a cycle free graph but let's safeguard
|
// 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 {
|
return verticesToMerge.map {
|
|
@ -7,8 +7,8 @@ internal extension Configuration.FileGraph {
|
||||||
case existing(path: String)
|
case existing(path: String)
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - Vertex
|
// MARK: - Vertix
|
||||||
class Vertex: Hashable {
|
class Vertix: Hashable {
|
||||||
internal let originalRemoteString: String?
|
internal let originalRemoteString: String?
|
||||||
internal var originatesFromRemote: Bool { originalRemoteString != nil }
|
internal var originatesFromRemote: Bool { originalRemoteString != nil }
|
||||||
internal var rootDirectory: String {
|
internal var rootDirectory: String {
|
||||||
|
@ -22,11 +22,11 @@ internal extension Configuration.FileGraph {
|
||||||
}
|
}
|
||||||
|
|
||||||
private let originalRootDirectory: String
|
private let originalRootDirectory: String
|
||||||
let isInitialVertex: Bool
|
let isInitialVertix: Bool
|
||||||
private(set) var filePath: FilePath
|
private(set) var filePath: FilePath
|
||||||
private(set) var configurationDict: [String: Any] = [:]
|
private(set) var configurationDict: [String: Any] = [:]
|
||||||
|
|
||||||
init(string: String, rootDirectory: String, isInitialVertex: Bool) {
|
init(string: String, rootDirectory: String, isInitialVertix: Bool) {
|
||||||
originalRootDirectory = rootDirectory
|
originalRootDirectory = rootDirectory
|
||||||
if string.hasPrefix("http://") || string.hasPrefix("https://") {
|
if string.hasPrefix("http://") || string.hasPrefix("https://") {
|
||||||
originalRemoteString = string
|
originalRemoteString = string
|
||||||
|
@ -37,25 +37,25 @@ internal extension Configuration.FileGraph {
|
||||||
path: string.bridge().absolutePathRepresentation(rootDirectory: rootDirectory)
|
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.originalRemoteString = originalRemoteString
|
||||||
self.originalRootDirectory = originalRootDirectory
|
self.originalRootDirectory = originalRootDirectory
|
||||||
self.filePath = filePath
|
self.filePath = filePath
|
||||||
self.isInitialVertex = isInitialVertex
|
self.isInitialVertix = isInitialVertix
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func copy(withNewRootDirectory rootDirectory: String) -> Vertex {
|
internal func copy(withNewRootDirectory rootDirectory: String) -> Vertix {
|
||||||
let vertex = Vertex(
|
let vertix = Vertix(
|
||||||
originalRemoteString: originalRemoteString,
|
originalRemoteString: originalRemoteString,
|
||||||
originalRootDirectory: rootDirectory,
|
originalRootDirectory: rootDirectory,
|
||||||
filePath: filePath,
|
filePath: filePath,
|
||||||
isInitialVertex: isInitialVertex
|
isInitialVertix: isInitialVertix
|
||||||
)
|
)
|
||||||
vertex.configurationDict = configurationDict
|
vertix.configurationDict = configurationDict
|
||||||
return vertex
|
return vertix
|
||||||
}
|
}
|
||||||
|
|
||||||
internal func build(
|
internal func build(
|
||||||
|
@ -73,15 +73,15 @@ internal extension Configuration.FileGraph {
|
||||||
|
|
||||||
private func read(at path: String) throws -> String {
|
private func read(at path: String) throws -> String {
|
||||||
guard !path.isEmpty && FileManager.default.fileExists(atPath: path) else {
|
guard !path.isEmpty && FileManager.default.fileExists(atPath: path) else {
|
||||||
throw isInitialVertex ?
|
throw isInitialVertix ?
|
||||||
Issue.initialFileNotFound(path: path) :
|
ConfigurationError.initialFileNotFound(path: path) :
|
||||||
Issue.genericWarning("File \(path) can't be found.")
|
ConfigurationError.generic("File \(path) can't be found.")
|
||||||
}
|
}
|
||||||
|
|
||||||
return try String(contentsOfFile: path, encoding: .utf8)
|
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
|
return lhs.filePath == rhs.filePath
|
||||||
&& lhs.originalRemoteString == rhs.originalRemoteString
|
&& lhs.originalRemoteString == rhs.originalRemoteString
|
||||||
&& lhs.rootDirectory == rhs.rootDirectory
|
&& lhs.rootDirectory == rhs.rootDirectory
|
||||||
|
@ -96,8 +96,8 @@ internal extension Configuration.FileGraph {
|
||||||
|
|
||||||
// MARK: - Edge
|
// MARK: - Edge
|
||||||
struct Edge: Hashable {
|
struct Edge: Hashable {
|
||||||
var parent: Vertex!
|
var parent: Vertix!
|
||||||
var child: Vertex!
|
var child: Vertix!
|
||||||
}
|
}
|
||||||
|
|
||||||
// MARK: - EdgeType
|
// MARK: - EdgeType
|
|
@ -7,8 +7,7 @@ public extension Configuration {
|
||||||
case spaces(count: Int)
|
case spaces(count: Int)
|
||||||
|
|
||||||
/// The default indentation style if none is explicitly provided.
|
/// The default indentation style if none is explicitly provided.
|
||||||
@_spi(TestHelper)
|
static var `default` = spaces(count: 4)
|
||||||
public static var `default` = spaces(count: 4)
|
|
||||||
|
|
||||||
/// Creates an indentation style based on an untyped configuration value.
|
/// Creates an indentation style based on an untyped configuration value.
|
||||||
///
|
///
|
|
@ -1,11 +1,6 @@
|
||||||
import Foundation
|
import Foundation
|
||||||
|
|
||||||
extension Configuration {
|
extension Configuration {
|
||||||
public enum ExcludeBy {
|
|
||||||
case prefix
|
|
||||||
case paths(excludedPaths: [String])
|
|
||||||
}
|
|
||||||
|
|
||||||
// MARK: Lintable Paths
|
// MARK: Lintable Paths
|
||||||
/// Returns the files that can be linted by SwiftLint in the specified parent path.
|
/// Returns the files that can be linted by SwiftLint in the specified parent path.
|
||||||
///
|
///
|
||||||
|
@ -17,8 +12,8 @@ extension Configuration {
|
||||||
///
|
///
|
||||||
/// - returns: Files to lint.
|
/// - returns: Files to lint.
|
||||||
public func lintableFiles(inPath path: String, forceExclude: Bool,
|
public func lintableFiles(inPath path: String, forceExclude: Bool,
|
||||||
excludeBy: ExcludeBy) -> [SwiftLintFile] {
|
excludeByPrefix: Bool = false) -> [SwiftLintFile] {
|
||||||
return lintablePaths(inPath: path, forceExclude: forceExclude, excludeBy: excludeBy)
|
return lintablePaths(inPath: path, forceExclude: forceExclude, excludeByPrefix: excludeByPrefix)
|
||||||
.compactMap(SwiftLintFile.init(pathDeferringReading:))
|
.compactMap(SwiftLintFile.init(pathDeferringReading:))
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -35,33 +30,20 @@ extension Configuration {
|
||||||
internal func lintablePaths(
|
internal func lintablePaths(
|
||||||
inPath path: String,
|
inPath path: String,
|
||||||
forceExclude: Bool,
|
forceExclude: Bool,
|
||||||
excludeBy: ExcludeBy,
|
excludeByPrefix: Bool = false,
|
||||||
fileManager: LintableFileManager = FileManager.default
|
fileManager: LintableFileManager = FileManager.default
|
||||||
) -> [String] {
|
) -> [String] {
|
||||||
if fileManager.isFile(atPath: path) {
|
|
||||||
if forceExclude {
|
|
||||||
switch excludeBy {
|
|
||||||
case .prefix:
|
|
||||||
return filterExcludedPathsByPrefix(in: [path.absolutePathStandardized()])
|
|
||||||
case .paths(let excludedPaths):
|
|
||||||
return filterExcludedPaths(excludedPaths, in: [path.absolutePathStandardized()])
|
|
||||||
}
|
|
||||||
}
|
|
||||||
// If path is a file and we're not forcing excludes, skip filtering with excluded/included paths
|
// If path is a file and we're not forcing excludes, skip filtering with excluded/included paths
|
||||||
return [path]
|
if path.isFile && !forceExclude { return [path] }
|
||||||
}
|
|
||||||
|
|
||||||
let pathsForPath = includedPaths.isEmpty ? fileManager.filesToLint(inPath: path, rootDirectory: nil) : []
|
let pathsForPath = includedPaths.isEmpty ? fileManager.filesToLint(inPath: path, rootDirectory: nil) : []
|
||||||
let includedPaths = self.includedPaths
|
let includedPaths = self.includedPaths
|
||||||
.flatMap(Glob.resolveGlob)
|
.flatMap(Glob.resolveGlob)
|
||||||
.parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) }
|
.parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) }
|
||||||
|
|
||||||
switch excludeBy {
|
return excludeByPrefix
|
||||||
case .prefix:
|
? filterExcludedPathsByPrefix(in: pathsForPath, includedPaths)
|
||||||
return filterExcludedPathsByPrefix(in: pathsForPath, includedPaths)
|
: filterExcludedPaths(fileManager: fileManager, in: pathsForPath, includedPaths)
|
||||||
case .paths(let excludedPaths):
|
|
||||||
return filterExcludedPaths(excludedPaths, in: pathsForPath, includedPaths)
|
|
||||||
}
|
|
||||||
}
|
}
|
||||||
|
|
||||||
/// Returns an array of file paths after removing the excluded paths as defined by this configuration.
|
/// Returns an array of file paths after removing the excluded paths as defined by this configuration.
|
||||||
|
@ -71,7 +53,7 @@ extension Configuration {
|
||||||
///
|
///
|
||||||
/// - returns: The input paths after removing the excluded paths.
|
/// - returns: The input paths after removing the excluded paths.
|
||||||
public func filterExcludedPaths(
|
public func filterExcludedPaths(
|
||||||
_ excludedPaths: [String],
|
fileManager: LintableFileManager = FileManager.default,
|
||||||
in paths: [String]...
|
in paths: [String]...
|
||||||
) -> [String] {
|
) -> [String] {
|
||||||
let allPaths = paths.flatMap { $0 }
|
let allPaths = paths.flatMap { $0 }
|
||||||
|
@ -82,6 +64,7 @@ extension Configuration {
|
||||||
let result = NSMutableOrderedSet(array: allPaths)
|
let result = NSMutableOrderedSet(array: allPaths)
|
||||||
#endif
|
#endif
|
||||||
|
|
||||||
|
let excludedPaths = self.excludedPaths(fileManager: fileManager)
|
||||||
result.minusSet(Set(excludedPaths))
|
result.minusSet(Set(excludedPaths))
|
||||||
// swiftlint:disable:next force_cast
|
// swiftlint:disable:next force_cast
|
||||||
return result.map { $0 as! String }
|
return result.map { $0 as! String }
|
||||||
|
@ -108,7 +91,7 @@ extension Configuration {
|
||||||
/// - parameter fileManager: The file manager to get child paths in a given parent location.
|
/// - parameter fileManager: The file manager to get child paths in a given parent location.
|
||||||
///
|
///
|
||||||
/// - returns: The expanded excluded file paths.
|
/// - returns: The expanded excluded file paths.
|
||||||
public func excludedPaths(fileManager: LintableFileManager = FileManager.default) -> [String] {
|
private func excludedPaths(fileManager: LintableFileManager) -> [String] {
|
||||||
return excludedPaths
|
return excludedPaths
|
||||||
.flatMap(Glob.resolveGlob)
|
.flatMap(Glob.resolveGlob)
|
||||||
.parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) }
|
.parallelFlatMap { fileManager.filesToLint(inPath: $0, rootDirectory: rootDirectory) }
|
|
@ -6,8 +6,7 @@ import SourceKittenFramework
|
||||||
|
|
||||||
extension Configuration {
|
extension Configuration {
|
||||||
// MARK: - Methods: Merging
|
// MARK: - Methods: Merging
|
||||||
@_spi(TestHelper)
|
internal func merged(
|
||||||
public func merged(
|
|
||||||
withChild childConfiguration: Configuration,
|
withChild childConfiguration: Configuration,
|
||||||
rootDirectory: String
|
rootDirectory: String
|
||||||
) -> Configuration {
|
) -> Configuration {
|
||||||
|
@ -106,7 +105,7 @@ extension Configuration {
|
||||||
config = self
|
config = self
|
||||||
} else if
|
} else if
|
||||||
FileManager.default.fileExists(atPath: configurationSearchPath),
|
FileManager.default.fileExists(atPath: configurationSearchPath),
|
||||||
!fileGraph.includesFile(atPath: configurationSearchPath)
|
fileGraph.includesFile(atPath: configurationSearchPath) != true
|
||||||
{
|
{
|
||||||
// Use self merged with the nested config that was found
|
// Use self merged with the nested config that was found
|
||||||
// iff that nested config has not already been used to build the main config
|
// iff that nested config has not already been used to build the main config
|
|
@ -1,3 +1,5 @@
|
||||||
|
// swiftlint:disable inclusive_language - To ease a migration from the previous `whitelist_rules`
|
||||||
|
|
||||||
extension Configuration {
|
extension Configuration {
|
||||||
// MARK: - Subtypes
|
// MARK: - Subtypes
|
||||||
internal enum Key: String, CaseIterable {
|
internal enum Key: String, CaseIterable {
|
||||||
|
@ -9,8 +11,10 @@ extension Configuration {
|
||||||
case optInRules = "opt_in_rules"
|
case optInRules = "opt_in_rules"
|
||||||
case reporter = "reporter"
|
case reporter = "reporter"
|
||||||
case swiftlintVersion = "swiftlint_version"
|
case swiftlintVersion = "swiftlint_version"
|
||||||
|
case useNestedConfigs = "use_nested_configs" // deprecated, always enabled
|
||||||
case warningThreshold = "warning_threshold"
|
case warningThreshold = "warning_threshold"
|
||||||
case onlyRules = "only_rules"
|
case onlyRules = "only_rules"
|
||||||
|
case whitelistRules = "whitelist_rules" // deprecated in favor of onlyRules
|
||||||
case indentation = "indentation"
|
case indentation = "indentation"
|
||||||
case analyzerRules = "analyzer_rules"
|
case analyzerRules = "analyzer_rules"
|
||||||
case allowZeroLintableFiles = "allow_zero_lintable_files"
|
case allowZeroLintableFiles = "allow_zero_lintable_files"
|
||||||
|
@ -34,7 +38,7 @@ extension Configuration {
|
||||||
/// - parameter cachePath: The location of the persisted cache on disk.
|
/// - parameter cachePath: The location of the persisted cache on disk.
|
||||||
public init(
|
public init(
|
||||||
dict: [String: Any],
|
dict: [String: Any],
|
||||||
ruleList: RuleList = RuleRegistry.shared.list,
|
ruleList: RuleList = primaryRuleList,
|
||||||
enableAllRules: Bool = false,
|
enableAllRules: Bool = false,
|
||||||
cachePath: String? = nil
|
cachePath: String? = nil
|
||||||
) throws {
|
) throws {
|
||||||
|
@ -44,7 +48,8 @@ extension Configuration {
|
||||||
let optInRules = defaultStringArray(dict[Key.optInRules.rawValue] ?? dict[Key.enabledRules.rawValue])
|
let optInRules = defaultStringArray(dict[Key.optInRules.rawValue] ?? dict[Key.enabledRules.rawValue])
|
||||||
let disabledRules = defaultStringArray(dict[Key.disabledRules.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])
|
let analyzerRules = defaultStringArray(dict[Key.analyzerRules.rawValue])
|
||||||
|
|
||||||
Self.warnAboutInvalidKeys(configurationDictionary: dict, ruleList: ruleList)
|
Self.warnAboutInvalidKeys(configurationDictionary: dict, ruleList: ruleList)
|
||||||
|
@ -52,7 +57,6 @@ extension Configuration {
|
||||||
configurationDictionary: dict, disabledRules: disabledRules,
|
configurationDictionary: dict, disabledRules: disabledRules,
|
||||||
optInRules: optInRules, onlyRules: onlyRules, ruleList: ruleList
|
optInRules: optInRules, onlyRules: onlyRules, ruleList: ruleList
|
||||||
)
|
)
|
||||||
Self.warnAboutMisplacedAnalyzerRules(optInRules: optInRules, ruleList: ruleList)
|
|
||||||
|
|
||||||
let allRulesWrapped: [ConfigurationRuleWrapper]
|
let allRulesWrapped: [ConfigurationRuleWrapper]
|
||||||
do {
|
do {
|
||||||
|
@ -60,7 +64,7 @@ extension Configuration {
|
||||||
} catch let RuleListError.duplicatedConfigurations(ruleType) {
|
} catch let RuleListError.duplicatedConfigurations(ruleType) {
|
||||||
let aliases = ruleType.description.deprecatedAliases.map { "'\($0)'" }.joined(separator: ", ")
|
let aliases = ruleType.description.deprecatedAliases.map { "'\($0)'" }.joined(separator: ", ")
|
||||||
let identifier = ruleType.description.identifier
|
let identifier = ruleType.description.identifier
|
||||||
throw Issue.genericWarning(
|
throw ConfigurationError.generic(
|
||||||
"Multiple configurations found for '\(identifier)'. Check for any aliases: \(aliases)."
|
"Multiple configurations found for '\(identifier)'. Check for any aliases: \(aliases)."
|
||||||
)
|
)
|
||||||
}
|
}
|
||||||
|
@ -102,7 +106,8 @@ extension Configuration {
|
||||||
if let indentationStyle = Self.IndentationStyle(rawIndentation) {
|
if let indentationStyle = Self.IndentationStyle(rawIndentation) {
|
||||||
return indentationStyle
|
return indentationStyle
|
||||||
}
|
}
|
||||||
Issue.invalidConfiguration(ruleID: Key.indentation.rawValue).print()
|
|
||||||
|
queuedPrintError("Invalid configuration for '\(Key.indentation)'. Falling back to default.")
|
||||||
return .default
|
return .default
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -118,7 +123,23 @@ extension Configuration {
|
||||||
) {
|
) {
|
||||||
// Deprecation warning for "enabled_rules"
|
// Deprecation warning for "enabled_rules"
|
||||||
if dict[Key.enabledRules.rawValue] != nil {
|
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
|
// Deprecation warning for rules
|
||||||
|
@ -132,7 +153,10 @@ extension Configuration {
|
||||||
}
|
}
|
||||||
|
|
||||||
for (deprecatedIdentifier, identifier) in deprecatedUsages {
|
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 +164,7 @@ extension Configuration {
|
||||||
// Log an error when supplying invalid keys in the configuration dictionary
|
// Log an error when supplying invalid keys in the configuration dictionary
|
||||||
let invalidKeys = Set(dict.keys).subtracting(validKeys(ruleList: ruleList))
|
let invalidKeys = Set(dict.keys).subtracting(validKeys(ruleList: ruleList))
|
||||||
if invalidKeys.isNotEmpty {
|
if invalidKeys.isNotEmpty {
|
||||||
Issue.invalidConfigurationKeys(invalidKeys.sorted()).print()
|
queuedPrintError("warning: Configuration contains invalid keys:\n\(invalidKeys)")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
|
@ -155,7 +179,7 @@ extension Configuration {
|
||||||
continue
|
continue
|
||||||
}
|
}
|
||||||
|
|
||||||
let message = "Found a configuration for '\(identifier)' rule"
|
let message = "warning: Found a configuration for '\(identifier)' rule"
|
||||||
|
|
||||||
switch rulesMode {
|
switch rulesMode {
|
||||||
case .allEnabled:
|
case .allEnabled:
|
||||||
|
@ -163,32 +187,19 @@ extension Configuration {
|
||||||
|
|
||||||
case .only(let onlyRules):
|
case .only(let onlyRules):
|
||||||
if Set(onlyRules).isDisjoint(with: rule.description.allIdentifiers) {
|
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):
|
case let .default(disabled: disabledRules, optIn: optInRules):
|
||||||
if rule is OptInRule.Type, Set(optInRules).isDisjoint(with: rule.description.allIdentifiers) {
|
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) {
|
} 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)'.")
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
}
|
}
|
||||||
|
|
||||||
private static func warnAboutMisplacedAnalyzerRules(optInRules: [String], ruleList: RuleList) {
|
|
||||||
let analyzerRules = ruleList.list
|
|
||||||
.filter { $0.value.self is AnalyzerRule.Type }
|
|
||||||
.map(\.key)
|
|
||||||
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()
|
|
||||||
}
|
|
||||||
}
|
|
||||||
}
|
}
|
|
@ -65,7 +65,7 @@ internal extension Configuration.FileGraph.FilePath {
|
||||||
|
|
||||||
// Handle wrong url format
|
// Handle wrong url format
|
||||||
guard let url = URL(string: urlString) else {
|
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
|
// Load from url
|
||||||
|
@ -118,7 +118,7 @@ internal extension Configuration.FileGraph.FilePath {
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func handleMissingNetwork(urlString: String, cachedFilePath: String?) throws -> String {
|
private mutating func handleMissingNetwork(urlString: String, cachedFilePath: String?) throws -> String {
|
||||||
if let cachedFilePath {
|
if let cachedFilePath = cachedFilePath {
|
||||||
queuedPrintError(
|
queuedPrintError(
|
||||||
"warning: No internet connectivity: Unable to load remote config from \"\(urlString)\". "
|
"warning: No internet connectivity: Unable to load remote config from \"\(urlString)\". "
|
||||||
+ "Using cached version as a fallback."
|
+ "Using cached version as a fallback."
|
||||||
|
@ -126,7 +126,7 @@ internal extension Configuration.FileGraph.FilePath {
|
||||||
self = .existing(path: cachedFilePath)
|
self = .existing(path: cachedFilePath)
|
||||||
return cachedFilePath
|
return cachedFilePath
|
||||||
} else {
|
} else {
|
||||||
throw Issue.genericWarning(
|
throw ConfigurationError.generic(
|
||||||
"No internet connectivity: Unable to load remote config from \"\(urlString)\". "
|
"No internet connectivity: Unable to load remote config from \"\(urlString)\". "
|
||||||
+ "Also didn't found cached version to fallback to."
|
+ "Also didn't found cached version to fallback to."
|
||||||
)
|
)
|
||||||
|
@ -139,7 +139,7 @@ internal extension Configuration.FileGraph.FilePath {
|
||||||
taskDone: Bool,
|
taskDone: Bool,
|
||||||
timeout: TimeInterval
|
timeout: TimeInterval
|
||||||
) throws -> String {
|
) throws -> String {
|
||||||
if let cachedFilePath {
|
if let cachedFilePath = cachedFilePath {
|
||||||
if taskDone {
|
if taskDone {
|
||||||
queuedPrintError(
|
queuedPrintError(
|
||||||
"warning: Unable to load remote config from \"\(urlString)\". Using cached version as a fallback."
|
"warning: Unable to load remote config from \"\(urlString)\". Using cached version as a fallback."
|
||||||
|
@ -155,12 +155,12 @@ internal extension Configuration.FileGraph.FilePath {
|
||||||
return cachedFilePath
|
return cachedFilePath
|
||||||
} else {
|
} else {
|
||||||
if taskDone {
|
if taskDone {
|
||||||
throw Issue.genericWarning(
|
throw ConfigurationError.generic(
|
||||||
"Unable to load remote config from \"\(urlString)\". "
|
"Unable to load remote config from \"\(urlString)\". "
|
||||||
+ "Also didn't found cached version to fallback to."
|
+ "Also didn't found cached version to fallback to."
|
||||||
)
|
)
|
||||||
} else {
|
} else {
|
||||||
throw Issue.genericWarning(
|
throw ConfigurationError.generic(
|
||||||
"Timeout (\(timeout) sec): Unable to load remote config from \"\(urlString)\". "
|
"Timeout (\(timeout) sec): Unable to load remote config from \"\(urlString)\". "
|
||||||
+ "Also didn't found cached version to fallback to."
|
+ "Also didn't found cached version to fallback to."
|
||||||
)
|
)
|
||||||
|
@ -169,14 +169,12 @@ internal extension Configuration.FileGraph.FilePath {
|
||||||
}
|
}
|
||||||
|
|
||||||
private mutating func handleFileWriteFailure(urlString: String, cachedFilePath: String?) throws -> String {
|
private mutating func handleFileWriteFailure(urlString: String, cachedFilePath: String?) throws -> String {
|
||||||
if let cachedFilePath {
|
if let cachedFilePath = cachedFilePath {
|
||||||
queuedPrintError(
|
queuedPrintError("Unable to cache remote config from \"\(urlString)\". Using cached version as a fallback.")
|
||||||
"warning: Unable to cache remote config from \"\(urlString)\". Using cached version as a fallback."
|
|
||||||
)
|
|
||||||
self = .existing(path: cachedFilePath)
|
self = .existing(path: cachedFilePath)
|
||||||
return cachedFilePath
|
return cachedFilePath
|
||||||
} else {
|
} else {
|
||||||
throw Issue.genericWarning(
|
throw ConfigurationError.generic(
|
||||||
"Unable to cache remote config from \"\(urlString)\". Also didn't found cached version to fallback to."
|
"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),
|
contents: Data(newGitignoreAppendix.utf8),
|
||||||
attributes: [:]
|
attributes: [:]
|
||||||
) else {
|
) else {
|
||||||
throw Issue.genericWarning("Issue maintaining remote config cache.")
|
throw ConfigurationError.generic("Issue maintaining remote config cache.")
|
||||||
}
|
}
|
||||||
} else {
|
} else {
|
||||||
var contents = try String(contentsOfFile: Configuration.FileGraph.FilePath.gitignorePath, encoding: .utf8)
|
var contents = try String(contentsOfFile: Configuration.FileGraph.FilePath.gitignorePath, encoding: .utf8)
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
Reference in New Issue