From 904bad2d471c2fca2e9faff9e54152703a7bf38f Mon Sep 17 00:00:00 2001 From: Nick Fraioli Date: Tue, 23 Apr 2024 23:49:04 -0700 Subject: [PATCH 1/4] Update `file_name` rule to match fully-qualified names of nested types This PR is aimed to address Issue #5840. It does the following: 1. Allows the `file_name` rule to match nested types when using fully-qualified names, meaning naming the following file `Nested.MyType.swift` is no longer a violation: ``` // Nested.MyType.swift enum Nested { struct MyType { } } ``` 2. Introduces a new option `fully_qualified` to have the `file_name` rule enforce using fully-qualified names, meaning naming the above file `MyType.swift` instead of `Nested.MyType.swift` would become a violation where it wasn't before (naming the file `Nested.swift` would still not be a violation). --- CHANGELOG.md | 6 ++ .../Rules/Idiomatic/FileNameRule.swift | 81 +++++++++++++++++-- .../FileNameConfiguration.swift | 2 + .../default_rule_configurations.yml | 1 + .../FileNameRuleTests.swift | 57 +++++++++---- ...Multiple.Levels.Deeply.Nested.MyType.swift | 9 +++ .../FileNameRuleFixtures/MyType.swift | 3 + .../FileNameRuleFixtures/Nested.MyType.swift | 4 + 8 files changed, 138 insertions(+), 25 deletions(-) create mode 100644 Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Multiple.Levels.Deeply.Nested.MyType.swift create mode 100644 Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyType.swift create mode 100644 Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Nested.MyType.swift diff --git a/CHANGELOG.md b/CHANGELOG.md index b7f2100741..b48929c25f 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -63,6 +63,12 @@ [Martin Redington](https://github.com/mildm8nnered) [#5711](https://github.com/realm/SwiftLint/issues/5711) +* Fixes `file_name` rule to match fully-qualified names of nested types. + Additionally adds a `fully_qualified` boolean option to enforce that + file names match nested types only using their fully-qualified name. + [fraioli](https://github.com/fraioli) + [#issue_number](https://github.com/realm/SwiftLint/issues/issue_number) + ## 0.57.0: Squeaky Clean Cycle #### Breaking diff --git a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift index d3554619eb..68487b492b 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift @@ -35,7 +35,7 @@ struct FileNameRule: OptInRule, SourceKitFreeRule { } // Process nested type separator - let allDeclaredTypeNames = TypeNameCollectingVisitor(viewMode: .sourceAccurate) + let allDeclaredTypeNames = TypeNameCollectingVisitor(requireFullyQualifiedNames: configuration.fullyQualified) .walk(tree: file.syntaxTree, handler: \.names) .map { $0.replacingOccurrences(of: ".", with: configuration.nestedTypeSeparator) @@ -56,33 +56,98 @@ struct FileNameRule: OptInRule, SourceKitFreeRule { } private class TypeNameCollectingVisitor: SyntaxVisitor { + // All of a visited node's ancestor type names if that node is nested, starting with the furthest + // ancestor and ending with the direct parent + private var ancestorNames: [String] = [] + + // All of the type names found in the file private(set) var names: Set = [] + // If true, nested types are only allowed in the file name when used by their fully-qualified name + // (e.g. `My.Nested.Type` and not just `Type`) + private let requireFullyQualifiedNames: Bool + + init(requireFullyQualifiedNames: Bool) { + self.requireFullyQualifiedNames = requireFullyQualifiedNames + super.init(viewMode: .sourceAccurate) + } + + private func addVisitedNodeName(_ name: String) { + let fullyQualifiedName = (ancestorNames + [name]).joined(separator: ".") + names.insert(fullyQualifiedName) + + if !requireFullyQualifiedNames { + names.insert(name) + } + } + + override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { + ancestorNames.append(node.name.text) + return .visitChildren + } + override func visitPost(_ node: ClassDeclSyntax) { - names.insert(node.name.text) + ancestorNames.removeLast() + addVisitedNodeName(node.name.text) + } + + override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { + ancestorNames.append(node.name.text) + return .visitChildren } override func visitPost(_ node: ActorDeclSyntax) { - names.insert(node.name.text) + ancestorNames.removeLast() + addVisitedNodeName(node.name.text) + } + + override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { + ancestorNames.append(node.name.text) + return .visitChildren } override func visitPost(_ node: StructDeclSyntax) { - names.insert(node.name.text) + ancestorNames.removeLast() + addVisitedNodeName(node.name.text) + } + + override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { + ancestorNames.append(node.name.text) + return .visitChildren } override func visitPost(_ node: TypeAliasDeclSyntax) { - names.insert(node.name.text) + ancestorNames.removeLast() + addVisitedNodeName(node.name.text) + } + + override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { + ancestorNames.append(node.name.text) + return .visitChildren } override func visitPost(_ node: EnumDeclSyntax) { - names.insert(node.name.text) + ancestorNames.removeLast() + addVisitedNodeName(node.name.text) + } + + override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { + ancestorNames.append(node.name.text) + return .visitChildren } override func visitPost(_ node: ProtocolDeclSyntax) { - names.insert(node.name.text) + ancestorNames.removeLast() + addVisitedNodeName(node.name.text) + } + + override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { + ancestorNames.append(node.extendedType.trimmedDescription) + return .visitChildren } override func visitPost(_ node: ExtensionDeclSyntax) { - names.insert(node.extendedType.trimmedDescription) + ancestorNames.removeLast() + addVisitedNodeName(node.extendedType.trimmedDescription) } } diff --git a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift index 08d414eacd..dadde21e2c 100644 --- a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift +++ b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift @@ -14,4 +14,6 @@ struct FileNameConfiguration: SeverityBasedRuleConfiguration { private(set) var suffixPattern = "\\+.*" @ConfigurationElement(key: "nested_type_separator") private(set) var nestedTypeSeparator = "." + @ConfigurationElement(key: "fully_qualified") + private(set) var fullyQualified = false } diff --git a/Tests/IntegrationTests/default_rule_configurations.yml b/Tests/IntegrationTests/default_rule_configurations.yml index 57148068b8..c5e2b4e102 100644 --- a/Tests/IntegrationTests/default_rule_configurations.yml +++ b/Tests/IntegrationTests/default_rule_configurations.yml @@ -177,6 +177,7 @@ file_name: prefix_pattern: "" suffix_pattern: "\+.*" nested_type_separator: "." + fully_qualified: false file_name_no_space: severity: warning excluded: [] diff --git a/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift b/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift index 30f3167966..a2e9c5924c 100644 --- a/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift @@ -5,26 +5,33 @@ private let fixturesDirectory = "\(TestResources.path)/FileNameRuleFixtures" final class FileNameRuleTests: SwiftLintTestCase { private func validate(fileName: String, - excludedOverride: [String]? = nil, + excluded: [String]? = nil, prefixPattern: String? = nil, suffixPattern: String? = nil, - nestedTypeSeparator: String? = nil) throws -> [StyleViolation] { + nestedTypeSeparator: String? = nil, + fullyQualified: Bool = false) throws -> [StyleViolation] { let file = SwiftLintFile(path: fixturesDirectory.stringByAppendingPathComponent(fileName))! - let rule: FileNameRule - if let excluded = excludedOverride { - rule = try FileNameRule(configuration: ["excluded": excluded]) - } else if let prefixPattern, let suffixPattern { - rule = try FileNameRule(configuration: ["prefix_pattern": prefixPattern, "suffix_pattern": suffixPattern]) - } else if let prefixPattern { - rule = try FileNameRule(configuration: ["prefix_pattern": prefixPattern]) - } else if let suffixPattern { - rule = try FileNameRule(configuration: ["suffix_pattern": suffixPattern]) - } else if let nestedTypeSeparator { - rule = try FileNameRule(configuration: ["nested_type_separator": nestedTypeSeparator]) - } else { - rule = FileNameRule() + + var configuration = [String: Any]() + + if let excluded { + configuration["excluded"] = excluded + } + if let prefixPattern { + configuration["prefix_pattern"] = prefixPattern + } + if let suffixPattern { + configuration["suffix_pattern"] = suffixPattern + } + if let nestedTypeSeparator { + configuration["nested_type_separator"] = nestedTypeSeparator + } + if fullyQualified { + configuration["fully_qualified"] = fullyQualified } + let rule = try FileNameRule(configuration: configuration) + return rule.validate(file: file) } @@ -52,6 +59,22 @@ final class FileNameRuleTests: SwiftLintTestCase { XCTAssert(try validate(fileName: "Notification.Name+Extension.swift").isEmpty) } + func testNestedTypeDoesntTrigger() { + XCTAssert(try validate(fileName: "Nested.MyType.swift").isEmpty) + } + + func testMultipleLevelsDeeplyNestedTypeDoesntTrigger() { + XCTAssert(try validate(fileName: "Multiple.Levels.Deeply.Nested.MyType.swift").isEmpty) + } + + func testNestedTypeNotFullyQualifiedDoesntTrigger() { + XCTAssert(try validate(fileName: "MyType.swift").isEmpty) + } + + func testNestedTypeNotFullyQualifiedDoesTriggerWithOverride() { + XCTAssert(try !validate(fileName: "MyType.swift", fullyQualified: true).isEmpty) + } + func testNestedTypeSeparatorDoesntTrigger() { XCTAssert(try validate(fileName: "NotificationName+Extension.swift", nestedTypeSeparator: "").isEmpty) XCTAssert(try validate(fileName: "Notification__Name+Extension.swift", nestedTypeSeparator: "__").isEmpty) @@ -67,11 +90,11 @@ final class FileNameRuleTests: SwiftLintTestCase { } func testMisspelledNameDoesntTriggerWithOverride() { - XCTAssert(try validate(fileName: "MyStructf.swift", excludedOverride: ["MyStructf.swift"]).isEmpty) + XCTAssert(try validate(fileName: "MyStructf.swift", excluded: ["MyStructf.swift"]).isEmpty) } func testMainDoesTriggerWithoutOverride() { - XCTAssertEqual(try validate(fileName: "main.swift", excludedOverride: []).count, 1) + XCTAssertEqual(try validate(fileName: "main.swift", excluded: []).count, 1) } func testCustomSuffixPattern() { diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Multiple.Levels.Deeply.Nested.MyType.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Multiple.Levels.Deeply.Nested.MyType.swift new file mode 100644 index 0000000000..46bb755a47 --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Multiple.Levels.Deeply.Nested.MyType.swift @@ -0,0 +1,9 @@ +extension Multiple { + enum Levels { + class Deeply { + struct Nested { + actor MyType {} + } + } + } +} diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyType.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyType.swift new file mode 100644 index 0000000000..7c72b6af9d --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/MyType.swift @@ -0,0 +1,3 @@ +enum Nested { + struct MyType {} +} diff --git a/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Nested.MyType.swift b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Nested.MyType.swift new file mode 100644 index 0000000000..a57866f72a --- /dev/null +++ b/Tests/SwiftLintFrameworkTests/Resources/FileNameRuleFixtures/Nested.MyType.swift @@ -0,0 +1,4 @@ +enum Nested { + struct MyType { + } +} From 8e375e7103d45a1200b3467b0e66911d90b958d2 Mon Sep 17 00:00:00 2001 From: Nick Fraioli Date: Tue, 12 Nov 2024 09:59:32 -0800 Subject: [PATCH 2/4] Address review comments --- CHANGELOG.md | 6 +- .../Rules/Idiomatic/FileNameRule.swift | 94 ++++++++++++------- .../FileNameConfiguration.swift | 4 +- .../default_rule_configurations.yml | 2 +- .../FileNameRuleTests.swift | 12 +-- 5 files changed, 70 insertions(+), 48 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index b48929c25f..3e614e761d 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -64,10 +64,10 @@ [#5711](https://github.com/realm/SwiftLint/issues/5711) * Fixes `file_name` rule to match fully-qualified names of nested types. - Additionally adds a `fully_qualified` boolean option to enforce that - file names match nested types only using their fully-qualified name. + Additionally adds a `require_fully_qualified_names` boolean option to enforce + that file names match nested types only using their fully-qualified name. [fraioli](https://github.com/fraioli) - [#issue_number](https://github.com/realm/SwiftLint/issues/issue_number) + [#5840](https://github.com/realm/SwiftLint/issues/5840) ## 0.57.0: Squeaky Clean Cycle diff --git a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift index 68487b492b..0eeadc1a2b 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift @@ -35,7 +35,9 @@ struct FileNameRule: OptInRule, SourceKitFreeRule { } // Process nested type separator - let allDeclaredTypeNames = TypeNameCollectingVisitor(requireFullyQualifiedNames: configuration.fullyQualified) + let allDeclaredTypeNames = TypeNameCollectingVisitor( + requireFullyQualifiedNames: configuration.requireFullyQualifiedNames + ) .walk(tree: file.syntaxTree, handler: \.names) .map { $0.replacingOccurrences(of: ".", with: configuration.nestedTypeSeparator) @@ -56,15 +58,15 @@ struct FileNameRule: OptInRule, SourceKitFreeRule { } private class TypeNameCollectingVisitor: SyntaxVisitor { - // All of a visited node's ancestor type names if that node is nested, starting with the furthest - // ancestor and ending with the direct parent - private var ancestorNames: [String] = [] + /// All of a visited node's ancestor type names if that node is nested, starting with the furthest + /// ancestor and ending with the direct parent + private var ancestorNames = Stack() - // All of the type names found in the file + /// All of the type names found in the file private(set) var names: Set = [] - // If true, nested types are only allowed in the file name when used by their fully-qualified name - // (e.g. `My.Nested.Type` and not just `Type`) + /// If true, nested types are only allowed in the file name when used by their fully-qualified name + /// (e.g. `My.Nested.Type` and not just `Type`) private let requireFullyQualifiedNames: Bool init(requireFullyQualifiedNames: Bool) { @@ -72,82 +74,102 @@ private class TypeNameCollectingVisitor: SyntaxVisitor { super.init(viewMode: .sourceAccurate) } - private func addVisitedNodeName(_ name: String) { + /// Visits a node to collect its type name and store it as an ancestor type name to prepend to any + /// children to form their fully-qualified type names + private func visitNode(_ node: some TypeNameCollectible) -> SyntaxVisitorContinueKind { + let name = node.typeName let fullyQualifiedName = (ancestorNames + [name]).joined(separator: ".") names.insert(fullyQualifiedName) if !requireFullyQualifiedNames { names.insert(name) } + + ancestorNames.push(node.typeName) + return .visitChildren + } + + /// Removes a node's type name as an ancestor type name once all of its children have been visited + private func visitNodePost(_: some TypeNameCollectible) { + ancestorNames.pop() } override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - ancestorNames.append(node.name.text) - return .visitChildren + visitNode(node) } override func visitPost(_ node: ClassDeclSyntax) { - ancestorNames.removeLast() - addVisitedNodeName(node.name.text) + visitNodePost(node) } override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { - ancestorNames.append(node.name.text) - return .visitChildren + visitNode(node) } override func visitPost(_ node: ActorDeclSyntax) { - ancestorNames.removeLast() - addVisitedNodeName(node.name.text) + visitNodePost(node) } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - ancestorNames.append(node.name.text) - return .visitChildren + visitNode(node) } override func visitPost(_ node: StructDeclSyntax) { - ancestorNames.removeLast() - addVisitedNodeName(node.name.text) + visitNodePost(node) } override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { - ancestorNames.append(node.name.text) - return .visitChildren + visitNode(node) } override func visitPost(_ node: TypeAliasDeclSyntax) { - ancestorNames.removeLast() - addVisitedNodeName(node.name.text) + visitNodePost(node) } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - ancestorNames.append(node.name.text) - return .visitChildren + visitNode(node) } override func visitPost(_ node: EnumDeclSyntax) { - ancestorNames.removeLast() - addVisitedNodeName(node.name.text) + visitNodePost(node) } override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - ancestorNames.append(node.name.text) - return .visitChildren + visitNode(node) } override func visitPost(_ node: ProtocolDeclSyntax) { - ancestorNames.removeLast() - addVisitedNodeName(node.name.text) + visitNodePost(node) } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - ancestorNames.append(node.extendedType.trimmedDescription) - return .visitChildren + visitNode(node) } override func visitPost(_ node: ExtensionDeclSyntax) { - ancestorNames.removeLast() - addVisitedNodeName(node.extendedType.trimmedDescription) + visitNodePost(node) + } +} + +/// A protocol for types that have a type name that can be collected +private protocol TypeNameCollectible { + var typeName: String { get } +} + +extension TypeNameCollectible where Self: NamedDeclSyntax { + var typeName: String { + name.trimmedDescription + } +} +extension ClassDeclSyntax: TypeNameCollectible {} +extension ActorDeclSyntax: TypeNameCollectible {} +extension StructDeclSyntax: TypeNameCollectible {} +extension TypeAliasDeclSyntax: TypeNameCollectible {} +extension EnumDeclSyntax: TypeNameCollectible {} +extension ProtocolDeclSyntax: TypeNameCollectible {} + +extension ExtensionDeclSyntax: TypeNameCollectible { + public var typeName: String { + extendedType.trimmedDescription } } diff --git a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift index dadde21e2c..7aa8be61b5 100644 --- a/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift +++ b/Source/SwiftLintBuiltInRules/Rules/RuleConfigurations/FileNameConfiguration.swift @@ -14,6 +14,6 @@ struct FileNameConfiguration: SeverityBasedRuleConfiguration { private(set) var suffixPattern = "\\+.*" @ConfigurationElement(key: "nested_type_separator") private(set) var nestedTypeSeparator = "." - @ConfigurationElement(key: "fully_qualified") - private(set) var fullyQualified = false + @ConfigurationElement(key: "require_fully_qualified_names") + private(set) var requireFullyQualifiedNames = false } diff --git a/Tests/IntegrationTests/default_rule_configurations.yml b/Tests/IntegrationTests/default_rule_configurations.yml index c5e2b4e102..ed843a3638 100644 --- a/Tests/IntegrationTests/default_rule_configurations.yml +++ b/Tests/IntegrationTests/default_rule_configurations.yml @@ -177,7 +177,7 @@ file_name: prefix_pattern: "" suffix_pattern: "\+.*" nested_type_separator: "." - fully_qualified: false + require_fully_qualified_names: false file_name_no_space: severity: warning excluded: [] diff --git a/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift b/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift index a2e9c5924c..4e3a2923fa 100644 --- a/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift +++ b/Tests/SwiftLintFrameworkTests/FileNameRuleTests.swift @@ -9,7 +9,7 @@ final class FileNameRuleTests: SwiftLintTestCase { prefixPattern: String? = nil, suffixPattern: String? = nil, nestedTypeSeparator: String? = nil, - fullyQualified: Bool = false) throws -> [StyleViolation] { + requireFullyQualifiedNames: Bool = false) throws -> [StyleViolation] { let file = SwiftLintFile(path: fixturesDirectory.stringByAppendingPathComponent(fileName))! var configuration = [String: Any]() @@ -26,8 +26,8 @@ final class FileNameRuleTests: SwiftLintTestCase { if let nestedTypeSeparator { configuration["nested_type_separator"] = nestedTypeSeparator } - if fullyQualified { - configuration["fully_qualified"] = fullyQualified + if requireFullyQualifiedNames { + configuration["require_fully_qualified_names"] = requireFullyQualifiedNames } let rule = try FileNameRule(configuration: configuration) @@ -72,7 +72,7 @@ final class FileNameRuleTests: SwiftLintTestCase { } func testNestedTypeNotFullyQualifiedDoesTriggerWithOverride() { - XCTAssert(try !validate(fileName: "MyType.swift", fullyQualified: true).isEmpty) + XCTAssert(try validate(fileName: "MyType.swift", requireFullyQualifiedNames: true).isNotEmpty) } func testNestedTypeSeparatorDoesntTrigger() { @@ -81,8 +81,8 @@ final class FileNameRuleTests: SwiftLintTestCase { } func testWrongNestedTypeSeparatorDoesTrigger() { - XCTAssert(try !validate(fileName: "Notification__Name+Extension.swift", nestedTypeSeparator: ".").isEmpty) - XCTAssert(try !validate(fileName: "NotificationName+Extension.swift", nestedTypeSeparator: "__").isEmpty) + XCTAssert(try validate(fileName: "Notification__Name+Extension.swift", nestedTypeSeparator: ".").isNotEmpty) + XCTAssert(try validate(fileName: "NotificationName+Extension.swift", nestedTypeSeparator: "__").isNotEmpty) } func testMisspelledNameDoesTrigger() { From 474a48f8b44ac88204d126ddc0eb760b00631cee Mon Sep 17 00:00:00 2001 From: Nick Fraioli Date: Tue, 26 Nov 2024 10:29:52 -0800 Subject: [PATCH 3/4] Remove the helper protocol and just use a separate function that takes a String --- .../Rules/Idiomatic/FileNameRule.swift | 86 +++++++------------ 1 file changed, 32 insertions(+), 54 deletions(-) diff --git a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift index 0eeadc1a2b..fa8999122d 100644 --- a/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift +++ b/Source/SwiftLintBuiltInRules/Rules/Idiomatic/FileNameRule.swift @@ -74,102 +74,80 @@ private class TypeNameCollectingVisitor: SyntaxVisitor { super.init(viewMode: .sourceAccurate) } - /// Visits a node to collect its type name and store it as an ancestor type name to prepend to any - /// children to form their fully-qualified type names - private func visitNode(_ node: some TypeNameCollectible) -> SyntaxVisitorContinueKind { - let name = node.typeName + /// Calls `visit(name:)` using the name of the provided node + private func visit(node: some NamedDeclSyntax) -> SyntaxVisitorContinueKind { + visit(name: node.name.trimmedDescription) + } + + /// Visits a node with the provided name, storing that name as an ancestor type name to prepend to + /// any children to form their fully-qualified names + private func visit(name: String) -> SyntaxVisitorContinueKind { let fullyQualifiedName = (ancestorNames + [name]).joined(separator: ".") names.insert(fullyQualifiedName) + // If the options don't require only fully-qualified names, then we will allow this node's + // name to be used by itself if !requireFullyQualifiedNames { names.insert(name) } - ancestorNames.push(node.typeName) + ancestorNames.push(name) return .visitChildren } - /// Removes a node's type name as an ancestor type name once all of its children have been visited - private func visitNodePost(_: some TypeNameCollectible) { - ancestorNames.pop() - } - override func visit(_ node: ClassDeclSyntax) -> SyntaxVisitorContinueKind { - visitNode(node) + visit(node: node) } - override func visitPost(_ node: ClassDeclSyntax) { - visitNodePost(node) + override func visitPost(_: ClassDeclSyntax) { + ancestorNames.pop() } override func visit(_ node: ActorDeclSyntax) -> SyntaxVisitorContinueKind { - visitNode(node) + visit(node: node) } - override func visitPost(_ node: ActorDeclSyntax) { - visitNodePost(node) + override func visitPost(_: ActorDeclSyntax) { + ancestorNames.pop() } override func visit(_ node: StructDeclSyntax) -> SyntaxVisitorContinueKind { - visitNode(node) + visit(node: node) } - override func visitPost(_ node: StructDeclSyntax) { - visitNodePost(node) + override func visitPost(_: StructDeclSyntax) { + ancestorNames.pop() } override func visit(_ node: TypeAliasDeclSyntax) -> SyntaxVisitorContinueKind { - visitNode(node) + visit(node: node) } - override func visitPost(_ node: TypeAliasDeclSyntax) { - visitNodePost(node) + override func visitPost(_: TypeAliasDeclSyntax) { + ancestorNames.pop() } override func visit(_ node: EnumDeclSyntax) -> SyntaxVisitorContinueKind { - visitNode(node) + visit(node: node) } - override func visitPost(_ node: EnumDeclSyntax) { - visitNodePost(node) + override func visitPost(_: EnumDeclSyntax) { + ancestorNames.pop() } override func visit(_ node: ProtocolDeclSyntax) -> SyntaxVisitorContinueKind { - visitNode(node) + visit(node: node) } - override func visitPost(_ node: ProtocolDeclSyntax) { - visitNodePost(node) + override func visitPost(_: ProtocolDeclSyntax) { + ancestorNames.pop() } override func visit(_ node: ExtensionDeclSyntax) -> SyntaxVisitorContinueKind { - visitNode(node) + visit(name: node.extendedType.trimmedDescription) } - override func visitPost(_ node: ExtensionDeclSyntax) { - visitNodePost(node) - } -} - -/// A protocol for types that have a type name that can be collected -private protocol TypeNameCollectible { - var typeName: String { get } -} - -extension TypeNameCollectible where Self: NamedDeclSyntax { - var typeName: String { - name.trimmedDescription - } -} -extension ClassDeclSyntax: TypeNameCollectible {} -extension ActorDeclSyntax: TypeNameCollectible {} -extension StructDeclSyntax: TypeNameCollectible {} -extension TypeAliasDeclSyntax: TypeNameCollectible {} -extension EnumDeclSyntax: TypeNameCollectible {} -extension ProtocolDeclSyntax: TypeNameCollectible {} - -extension ExtensionDeclSyntax: TypeNameCollectible { - public var typeName: String { - extendedType.trimmedDescription + override func visitPost(_: ExtensionDeclSyntax) { + ancestorNames.pop() } } From 52f45c36b36d33924739131ee8a869aefef1a04c Mon Sep 17 00:00:00 2001 From: Nick Fraioli Date: Tue, 26 Nov 2024 12:00:11 -0800 Subject: [PATCH 4/4] Add two spaces after change description in CHANGELOG.md --- CHANGELOG.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 3e614e761d..04e01e31e1 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -65,7 +65,7 @@ * Fixes `file_name` rule to match fully-qualified names of nested types. Additionally adds a `require_fully_qualified_names` boolean option to enforce - that file names match nested types only using their fully-qualified name. + that file names match nested types only using their fully-qualified name. [fraioli](https://github.com/fraioli) [#5840](https://github.com/realm/SwiftLint/issues/5840)