From 0381d8f2456bdd669ee372a9d9b6b8d9725d440e Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Mateusz=20Ba=CC=A8k?= <44930823+Matejkob@users.noreply.github.com> Date: Sun, 6 Aug 2023 10:58:34 +0200 Subject: [PATCH] Introduce type naming for Child nodes in CodeGeneration --- .../Sources/SyntaxSupport/Child.swift | 29 +++ .../Sources/SyntaxSupport/Node.swift | 22 ++- .../Sources/Utils/SyntaxBuildableChild.swift | 20 +- .../Sources/Utils/SyntaxBuildableType.swift | 12 +- .../LayoutNode+Extensions.swift | 18 +- .../templates/Array+Child.swift | 2 +- .../swiftparser/ParserTokenSpecSetFile.swift | 2 +- .../swiftsyntax/RawSyntaxNodesFile.swift | 14 +- .../swiftsyntax/RawSyntaxValidationFile.swift | 8 +- .../RenamedChildrenCompatibilityFile.swift | 6 +- .../swiftsyntax/SyntaxNodesFile.swift | 6 +- ...amedChildrenBuilderCompatibilityFile.swift | 2 +- .../ValidateSyntaxNodes.swift | 183 +++++++++--------- 13 files changed, 179 insertions(+), 145 deletions(-) diff --git a/CodeGeneration/Sources/SyntaxSupport/Child.swift b/CodeGeneration/Sources/SyntaxSupport/Child.swift index 53360817d14..97592aed797 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Child.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Child.swift @@ -46,6 +46,14 @@ public enum ChildKind { } } + public var isToken: Bool { + if case .token = self { + return true + } else { + return false + } + } + public var isNodeChoicesEmpty: Bool { if case .nodeChoices(let nodeChoices) = self { return nodeChoices.isEmpty @@ -103,6 +111,22 @@ public class Child { return .identifier(lowercaseFirstWord(name: name)) } + /// If this child has node choices, the type that the nested `SyntaxChildChoices` type should get. + /// + /// For any other kind of child nodes, accessing this property crashes. + public var syntaxChoicesType: TypeSyntax { + precondition(kind.isNodeChoices, "Cannot get `syntaxChoicesType` for node that doesn’t have nodeChoices") + return "\(raw: name.withFirstCharacterUppercased)" + } + + /// If this child only has tokens, the type that the generated `TokenSpecSet` should get. + /// + /// For any other kind of child nodes, accessing this property crashes. + public var tokenSpecSetType: TypeSyntax { + precondition(kind.isToken, "Cannot get `tokenSpecSetType` for node that isn’t a token") + return "\(raw: name.withFirstCharacterUppercased)Options" + } + /// The deprecated name of this child that's suitable to be used for variable or enum case names. public var deprecatedVarName: TokenSyntax? { guard let deprecatedName = deprecatedName else { @@ -111,6 +135,11 @@ public class Child { return .identifier(lowercaseFirstWord(name: deprecatedName)) } + /// Determines if this child has a deprecated name + public var hasDeprecatedName: Bool { + return deprecatedName != nil + } + /// If the child ends with "token" in the kind, it's considered a token node. /// Grab the existing reference to that token from the global list. public var tokenKind: Token? { diff --git a/CodeGeneration/Sources/SyntaxSupport/Node.swift b/CodeGeneration/Sources/SyntaxSupport/Node.swift index b32b019fa63..def511438d3 100644 --- a/CodeGeneration/Sources/SyntaxSupport/Node.swift +++ b/CodeGeneration/Sources/SyntaxSupport/Node.swift @@ -114,18 +114,20 @@ public class Node { // any two defined children childrenWithUnexpected = children.enumerated().flatMap { (i, child) -> [Child] in + let childName = child.name.withFirstCharacterUppercased + let unexpectedName: String let unexpectedDeprecatedName: String? if i == 0 { - unexpectedName = "UnexpectedBefore\(child.name)" - unexpectedDeprecatedName = child.deprecatedName.map { "UnexpectedBefore\($0)" } + unexpectedName = "UnexpectedBefore\(childName)" + unexpectedDeprecatedName = child.deprecatedName.map { "UnexpectedBefore\($0.withFirstCharacterUppercased)" } } else { - unexpectedName = "UnexpectedBetween\(children[i - 1].name)And\(child.name)" - if let deprecatedName = children[i - 1].deprecatedName { - unexpectedDeprecatedName = "UnexpectedBetween\(deprecatedName)And\(child.deprecatedName ?? child.name)" - } else if let deprecatedName = child.deprecatedName { - unexpectedDeprecatedName = "UnexpectedBetween\(children[i - 1].name)And\(deprecatedName)" + unexpectedName = "UnexpectedBetween\(children[i - 1].name.withFirstCharacterUppercased)And\(childName)" + if let deprecatedName = children[i - 1].deprecatedName?.withFirstCharacterUppercased { + unexpectedDeprecatedName = "UnexpectedBetween\(deprecatedName)And\(child.deprecatedName?.withFirstCharacterUppercased ?? childName)" + } else if let deprecatedName = child.deprecatedName?.withFirstCharacterUppercased { + unexpectedDeprecatedName = "UnexpectedBetween\(children[i - 1].name.withFirstCharacterUppercased)And\(deprecatedName)" } else { unexpectedDeprecatedName = nil } @@ -139,9 +141,9 @@ public class Node { return [unexpectedBefore, child] } + [ Child( - name: "UnexpectedAfter\(children.last!.name)", - deprecatedName: children.last!.deprecatedName.map { "UnexpectedAfter\($0)" }, - kind: .collection(kind: .unexpectedNodes, collectionElementName: "UnexpectedAfter\(children.last!.name)"), + name: "UnexpectedAfter\(children.last!.name.withFirstCharacterUppercased)", + deprecatedName: children.last!.deprecatedName.map { "UnexpectedAfter\($0.withFirstCharacterUppercased)" }, + kind: .collection(kind: .unexpectedNodes, collectionElementName: "UnexpectedAfter\(children.last!.name.withFirstCharacterUppercased)"), isOptional: true ) ] diff --git a/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift b/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift index 7b70ed78bee..8df7e047a0a 100644 --- a/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift +++ b/CodeGeneration/Sources/Utils/SyntaxBuildableChild.swift @@ -26,7 +26,7 @@ public enum SyntaxOrTokenNodeKind: Hashable { public extension Child { /// The type of this child, represented by a ``SyntaxBuildableType``, which can /// be used to create the corresponding `Buildable` and `ExpressibleAs` types. - var type: SyntaxBuildableType { + var buildableType: SyntaxBuildableType { let buildableKind: SyntaxOrTokenNodeKind switch kind { case .node(kind: let kind): @@ -44,29 +44,29 @@ public extension Child { ) } - var parameterBaseType: String { + var parameterBaseType: TypeSyntax { switch kind { case .nodeChoices: - return self.name + return self.syntaxChoicesType default: - return type.parameterBaseType + return buildableType.parameterBaseType } } var parameterType: TypeSyntax { - return self.type.optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(parameterBaseType))) + return self.buildableType.optionalWrapped(type: parameterBaseType) } var defaultValue: ExprSyntax? { if isOptional || isUnexpectedNodes { - if type.isBaseType && kind.isNodeChoicesEmpty { - return ExprSyntax("\(type.buildable).none") + if buildableType.isBaseType && kind.isNodeChoicesEmpty { + return ExprSyntax("\(buildableType.buildable).none") } else { return ExprSyntax("nil") } } guard let token = token, isToken else { - return type.defaultValue + return buildableType.defaultValue } if token.text != nil { return ExprSyntax(".\(token.varOrCaseName)Token()") @@ -128,7 +128,7 @@ public extension Child { } var preconditionChoices: [ExprSyntax] = [] - if type.isOptional { + if buildableType.isOptional { preconditionChoices.append( ExprSyntax( SequenceExprSyntax { @@ -143,7 +143,7 @@ public extension Child { preconditionChoices.append( ExprSyntax( SequenceExprSyntax { - MemberAccessExprSyntax(base: type.forceUnwrappedIfNeeded(expr: DeclReferenceExprSyntax(baseName: .identifier(varName))), name: "text") + MemberAccessExprSyntax(base: buildableType.forceUnwrappedIfNeeded(expr: DeclReferenceExprSyntax(baseName: .identifier(varName))), name: "text") BinaryOperatorExprSyntax(text: "==") StringLiteralExprSyntax(content: textChoice) } diff --git a/CodeGeneration/Sources/Utils/SyntaxBuildableType.swift b/CodeGeneration/Sources/Utils/SyntaxBuildableType.swift index e6aef5e2e51..8b138ab9874 100644 --- a/CodeGeneration/Sources/Utils/SyntaxBuildableType.swift +++ b/CodeGeneration/Sources/Utils/SyntaxBuildableType.swift @@ -85,7 +85,7 @@ public struct SyntaxBuildableType: Hashable { /// - For token: ``TokenSyntax`` (tokens don't have a dedicated type in SwiftSyntaxBuilder) /// If the type is optional, the type is wrapped in an `OptionalType`. public var buildable: TypeSyntax { - optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(syntaxBaseName))) + optionalWrapped(type: syntaxBaseName) } /// Whether parameters of this type should be initializable by a result builder. @@ -120,10 +120,10 @@ public struct SyntaxBuildableType: Hashable { /// The corresponding `*Syntax` type defined in the `SwiftSyntax` module, /// without any question marks attached. - public var syntaxBaseName: String { + public var syntaxBaseName: TypeSyntax { switch kind { case .node(kind: let kind): - return "\(kind.syntaxType)" + return kind.syntaxType case .token: return "TokenSyntax" } @@ -133,12 +133,12 @@ public struct SyntaxBuildableType: Hashable { /// which will eventually get built from `SwiftSyntaxBuilder`. If the type /// is optional, this terminates with a `?`. public var syntax: TypeSyntax { - return optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(syntaxBaseName))) + return optionalWrapped(type: syntaxBaseName) } /// The type that is used for parameters in SwiftSyntaxBuilder that take this /// type of syntax node. - public var parameterBaseType: String { + public var parameterBaseType: TypeSyntax { if isBaseType { return "\(syntaxBaseName)Protocol" } else { @@ -147,7 +147,7 @@ public struct SyntaxBuildableType: Hashable { } public var parameterType: TypeSyntax { - return optionalWrapped(type: IdentifierTypeSyntax(name: .identifier(parameterBaseType))) + return optionalWrapped(type: parameterBaseType) } /// Assuming that this is a collection type, the non-optional type of the result builder diff --git a/CodeGeneration/Sources/generate-swiftsyntax/LayoutNode+Extensions.swift b/CodeGeneration/Sources/generate-swiftsyntax/LayoutNode+Extensions.swift index 67fc898c2d4..1c32ba580b5 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/LayoutNode+Extensions.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/LayoutNode+Extensions.swift @@ -24,7 +24,7 @@ extension LayoutNode { func createFunctionParameterSyntax(for child: Child) -> FunctionParameterSyntax { var paramType: TypeSyntax if !child.kind.isNodeChoicesEmpty { - paramType = "\(raw: child.name)" + paramType = "\(child.syntaxChoicesType)" } else if child.hasBaseType { paramType = "some \(raw: child.syntaxNodeKind.protocolType)" } else { @@ -119,16 +119,16 @@ extension LayoutNode { childName = child.varOrCaseName } - if child.type.isBuilderInitializable { + if child.buildableType.isBuilderInitializable { // Allow initializing certain syntax collections with result builders shouldCreateInitializer = true - let builderInitializableType = child.type.builderInitializableType - if child.type.builderInitializableType != child.type { - let param = Node.from(type: child.type).layoutNode!.singleNonDefaultedChild + let builderInitializableType = child.buildableType.builderInitializableType + if child.buildableType.builderInitializableType != child.buildableType { + let param = Node.from(type: child.buildableType).layoutNode!.singleNonDefaultedChild if child.isOptional { - produceExpr = ExprSyntax("\(childName)Builder().map { \(raw: child.type.syntaxBaseName)(\(param.varOrCaseName): $0) }") + produceExpr = ExprSyntax("\(childName)Builder().map { \(raw: child.buildableType.syntaxBaseName)(\(param.varOrCaseName): $0) }") } else { - produceExpr = ExprSyntax("\(raw: child.type.syntaxBaseName)(\(param.varOrCaseName): \(childName)Builder())") + produceExpr = ExprSyntax("\(raw: child.buildableType.syntaxBaseName)(\(param.varOrCaseName): \(childName)Builder())") } } else { produceExpr = ExprSyntax("\(childName)Builder()") @@ -195,8 +195,8 @@ fileprivate func convertFromSyntaxProtocolToSyntaxType(child: Child, useDeprecat childName = child.varOrCaseName } - if child.type.isBaseType && !child.kind.isNodeChoices { - return ExprSyntax("\(raw: child.type.syntaxBaseName)(fromProtocol: \(childName.backtickedIfNeeded))") + if child.buildableType.isBaseType && !child.kind.isNodeChoices { + return ExprSyntax("\(raw: child.buildableType.syntaxBaseName)(fromProtocol: \(childName.backtickedIfNeeded))") } return ExprSyntax("\(raw: childName.backtickedIfNeeded)") } diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/Array+Child.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/Array+Child.swift index 1f987e98ca1..3f13cf3f33c 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/Array+Child.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/Array+Child.swift @@ -14,6 +14,6 @@ import SyntaxSupport extension Array where Element == Child { var hasDeprecatedChild: Bool { - return self.contains(where: { $0.deprecatedName != nil }) + return self.contains(where: { $0.hasDeprecatedName }) } } diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserTokenSpecSetFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserTokenSpecSetFile.swift index b4115cf49cf..477bc7e2100 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserTokenSpecSetFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftparser/ParserTokenSpecSetFile.swift @@ -25,7 +25,7 @@ let parserTokenSpecSetFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { try EnumDeclSyntax( """ @_spi(Diagnostics) - public enum \(raw: child.name)Options: TokenSpecSet + public enum \(child.tokenSpecSetType): TokenSpecSet """ ) { for choice in choices { diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxNodesFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxNodesFile.swift index 1adf95123e6..b966b9e3c35 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxNodesFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxNodesFile.swift @@ -16,13 +16,13 @@ import SyntaxSupport import Utils fileprivate extension Node { - var childrenChoicesEnums: [(name: TokenSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])] { + var childrenChoicesEnums: [(name: TypeSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])] { let node = self if let node = node.layoutNode { - return node.children.compactMap { child -> (name: TokenSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])? in + return node.children.compactMap { child -> (name: TypeSyntax, choices: [(caseName: TokenSyntax, kind: SyntaxNodeKind)])? in switch child.kind { case .nodeChoices(let choices): - return (.identifier(child.name), choices.map { ($0.varOrCaseName, $0.syntaxNodeKind) }) + return (child.syntaxChoicesType, choices.map { ($0.varOrCaseName, $0.syntaxNodeKind) }) default: return nil } @@ -31,7 +31,7 @@ fileprivate extension Node { let choices = node.elementChoices.map { choice -> (TokenSyntax, SyntaxNodeKind) in (SYNTAX_NODE_MAP[choice]!.varOrCaseName, SYNTAX_NODE_MAP[choice]!.kind) } - return [(.identifier("Element"), choices)] + return [("Element", choices)] } else { return [] } @@ -238,7 +238,7 @@ let rawSyntaxNodesFile = SourceFileSyntax(leadingTrivia: copyrightHeader) { } for (index, child) in node.children.enumerated() { - try VariableDeclSyntax("public var \(child.varOrCaseName.backtickedIfNeeded): Raw\(raw: child.type.buildable)") { + try VariableDeclSyntax("public var \(child.varOrCaseName.backtickedIfNeeded): Raw\(raw: child.buildableType.buildable)") { let iuoMark = child.isOptional ? "" : "!" if child.syntaxNodeKind == .syntax { @@ -257,11 +257,11 @@ fileprivate extension Child { var rawParameterType: TypeSyntax { let paramType: TypeSyntax if case ChildKind.nodeChoices = kind { - paramType = "\(raw: name)" + paramType = syntaxChoicesType } else { paramType = syntaxNodeKind.rawType } - return type.optionalWrapped(type: paramType) + return buildableType.optionalWrapped(type: paramType) } } diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxValidationFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxValidationFile.swift index 17969fed29f..ef97656f8de 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxValidationFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RawSyntaxValidationFile.swift @@ -204,7 +204,7 @@ let rawSyntaxValidationFile = try! SourceFileSyntax(leadingTrivia: copyrightHead let verifiedChoices = ArrayExprSyntax { ArrayElementSyntax( leadingTrivia: .newline, - expression: ExprSyntax("verify(layout[\(raw: index)], as: Raw\(raw: child.type.buildable).self)") + expression: ExprSyntax("verify(layout[\(raw: index)], as: Raw\(raw: child.buildableType.buildable).self)") ) } @@ -220,10 +220,12 @@ let rawSyntaxValidationFile = try! SourceFileSyntax(leadingTrivia: copyrightHead } } } - let verifyCall = ExprSyntax("verify(layout[\(raw: index)], as: Raw\(raw: child.type.buildable).self, tokenChoices: \(choices))") + let verifyCall = ExprSyntax( + "verify(layout[\(raw: index)], as: Raw\(raw: child.buildableType.buildable).self, tokenChoices: \(choices))" + ) ExprSyntax("assertNoError(kind, \(raw: index), \(verifyCall))") default: - ExprSyntax("assertNoError(kind, \(raw: index), verify(layout[\(raw: index)], as: Raw\(raw: child.type.buildable).self))") + ExprSyntax("assertNoError(kind, \(raw: index), verify(layout[\(raw: index)], as: Raw\(raw: child.buildableType.buildable).self))") } } } else if let node = node.collectionNode { diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift index 33cb492a96f..07d2ad32224 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/RenamedChildrenCompatibilityFile.swift @@ -20,8 +20,8 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy try ExtensionDeclSyntax("extension \(raw: layoutNode.type.syntaxBaseName)") { for child in layoutNode.children { if let deprecatedVarName = child.deprecatedVarName { - let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : "\(raw: child.name)" - let type = child.isOptional ? TypeSyntax("\(raw: childType)?") : TypeSyntax("\(raw: childType)") + let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : child.syntaxChoicesType + let type = child.isOptional ? TypeSyntax("\(childType)?") : childType DeclSyntax( """ @@ -56,7 +56,7 @@ let renamedChildrenCompatibilityFile = try! SourceFileSyntax(leadingTrivia: copy } let deprecatedNames = layoutNode.children - .filter { !$0.isUnexpectedNodes && $0.deprecatedName != nil } + .filter { !$0.isUnexpectedNodes && $0.hasDeprecatedName } .map { $0.varOrCaseName.description } .joined(separator: ", ") diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift index a79fa7da8ff..417353f9e91 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntax/SyntaxNodesFile.swift @@ -103,7 +103,7 @@ func syntaxNode(emitKind: SyntaxNodeKind) -> SourceFileSyntax { for child in node.children { ArrayElementSyntax( expression: MemberAccessExprSyntax( - base: child.type.optionalChained(expr: ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded)")), + base: child.buildableType.optionalChained(expr: ExprSyntax("\(child.varOrCaseName.backtickedIfNeeded)")), period: .periodToken(), name: "raw" ) @@ -159,7 +159,7 @@ func syntaxNode(emitKind: SyntaxNodeKind) -> SourceFileSyntax { // Children properties // =================== - let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : "\(raw: child.name)" + let childType: TypeSyntax = child.kind.isNodeChoicesEmpty ? child.syntaxNodeKind.syntaxType : child.syntaxChoicesType let type = child.isOptional ? TypeSyntax("\(raw: childType)?") : TypeSyntax("\(raw: childType)") try! VariableDeclSyntax( @@ -244,7 +244,7 @@ private func generateSyntaxChildChoices(for child: Child) throws -> EnumDeclSynt return nil } - return try! EnumDeclSyntax("public enum \(raw: child.name): SyntaxChildChoices, SyntaxHashable") { + return try! EnumDeclSyntax("public enum \(child.syntaxChoicesType): SyntaxChildChoices, SyntaxHashable") { for choice in choices { DeclSyntax("case `\(choice.varOrCaseName)`(\(raw: choice.syntaxNodeKind.syntaxType))") } diff --git a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntaxbuilder/RenamedChildrenBuilderCompatibilityFile.swift b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntaxbuilder/RenamedChildrenBuilderCompatibilityFile.swift index aa082a58ef4..04d2e1cad2a 100644 --- a/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntaxbuilder/RenamedChildrenBuilderCompatibilityFile.swift +++ b/CodeGeneration/Sources/generate-swiftsyntax/templates/swiftsyntaxbuilder/RenamedChildrenBuilderCompatibilityFile.swift @@ -21,7 +21,7 @@ let renamedChildrenBuilderCompatibilityFile = try! SourceFileSyntax(leadingTrivi for layoutNode in SYNTAX_NODES.compactMap(\.layoutNode).filter({ $0.children.hasDeprecatedChild }) { if let convenienceInit = try layoutNode.createConvenienceBuilderInitializer(useDeprecatedChildName: true) { let deprecatedNames = layoutNode.children - .filter { !$0.isUnexpectedNodes && $0.deprecatedName != nil } + .filter { !$0.isUnexpectedNodes && $0.hasDeprecatedName } .compactMap { $0.varOrCaseName.description } .joined(separator: ", ") diff --git a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift index 22c1ed2cdd1..7abe5818316 100644 --- a/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift +++ b/CodeGeneration/Tests/ValidateSyntaxNodes/ValidateSyntaxNodes.swift @@ -81,12 +81,12 @@ fileprivate extension ChildKind { fileprivate extension Child { func hasSameType(as other: Child) -> Bool { - return name == other.name && kind.hasSameType(as: other.kind) && isOptional == other.isOptional + return varOrCaseName.description == other.varOrCaseName.description && kind.hasSameType(as: other.kind) && isOptional == other.isOptional } func isFollowedByColonToken(in node: LayoutNode) -> Bool { - guard let childIndex = node.children.firstIndex(where: { $0.name == self.name }) else { - preconditionFailure("\(self.name) is not a child of \(node.kind.syntaxType)") + guard let childIndex = node.children.firstIndex(where: { $0.varOrCaseName.description == self.varOrCaseName.description }) else { + preconditionFailure("\(self.varOrCaseName) is not a child of \(node.kind.syntaxType)") } guard childIndex + 2 < node.children.count else { return false @@ -168,13 +168,13 @@ class ValidateSyntaxNodes: XCTestCase { switch choice { case .keyword(text: let keyword): if child.isFollowedByColonToken(in: node) { - if child.name != "\(keyword.withFirstCharacterUppercased)Label" { + if child.varOrCaseName.description != "\(keyword)Label" { return - "child '\(child.name)' has a single keyword as its only token choice and is followed by a colon. It should thus be named '\(keyword.withFirstCharacterUppercased)Label'" + "child '\(child.varOrCaseName)' has a single keyword as its only token choice and is followed by a colon. It should thus be named '\(keyword)Label'" } } else { - if child.name != "\(keyword.withFirstCharacterUppercased)Keyword" { - return "child '\(child.name)' has a single keyword as its only token choice and should thus be named '\(keyword.withFirstCharacterUppercased)Keyword'" + if child.varOrCaseName.description != "\(keyword)Keyword" { + return "child '\(child.varOrCaseName)' has a single keyword as its only token choice and should thus be named '\(keyword)Keyword'" } } @@ -182,19 +182,20 @@ class ValidateSyntaxNodes: XCTestCase { // We allow arbitrary naming of identifiers and literals break case .token(.comma): - if child.name != "TrailingComma" && child.name != "Comma" { - return "child '\(child.name)' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'" + if child.varOrCaseName.description != "trailingComma" && child.varOrCaseName.description != "comma" { + return "child '\(child.varOrCaseName)' has a comma keyword as its only token choice and should thus be named 'comma' or 'trailingComma'" } case .token(let token): let expectedChildName = - token.spec.varOrCaseName.text.withFirstCharacterUppercased + token.spec.varOrCaseName.text .dropSuffix("Token") - .dropPrefix("Prefix") - .dropPrefix("Infix") - .dropPrefix("Postfix") - .dropPrefix("Binary") - if child.name != expectedChildName { - return "child '\(child.name)' has a token as its only token choice and should thus be named '\(expectedChildName)'" + .dropPrefix("prefix") + .dropPrefix("infix") + .dropPrefix("postfix") + .dropPrefix("binary") + .withFirstCharacterLowercased + if child.varOrCaseName.description != expectedChildName { + return "child '\(child.varOrCaseName)' has a token as its only token choice and should thus be named '\(expectedChildName)'" } } return nil @@ -220,27 +221,27 @@ class ValidateSyntaxNodes: XCTestCase { // Even though these nodes only accept e.g. 'async' name them consistently with properties that accept 'async' and 'reasync' ValidationFailure( node: .accessorEffectSpecifiers, - message: "child 'AsyncSpecifier' has a single keyword as its only token choice and should thus be named 'AsyncKeyword'" + message: "child 'asyncSpecifier' has a single keyword as its only token choice and should thus be named 'asyncKeyword'" // To be consistent with AsyncSpecifier properties that can be both 'async' and 'reasync' ), ValidationFailure( node: .accessorEffectSpecifiers, - message: "child 'ThrowsSpecifier' has a single keyword as its only token choice and should thus be named 'ThrowsKeyword'" + message: "child 'throwsSpecifier' has a single keyword as its only token choice and should thus be named 'throwsKeyword'" // To be consistent with AsyncSpecifier properties that can be both 'async' and 'reasync' ), ValidationFailure( node: .deinitializerEffectSpecifiers, - message: "child 'AsyncSpecifier' has a single keyword as its only token choice and should thus be named 'AsyncKeyword'" + message: "child 'asyncSpecifier' has a single keyword as its only token choice and should thus be named 'asyncKeyword'" // To be consistent with AsyncSpecifier properties that can be both 'async' and 'reasync' ), ValidationFailure( node: .typeEffectSpecifiers, - message: "child 'AsyncSpecifier' has a single keyword as its only token choice and should thus be named 'AsyncKeyword'" + message: "child 'asyncSpecifier' has a single keyword as its only token choice and should thus be named 'asyncKeyword'" // To be consistent with AsyncSpecifier properties that can be both 'async' and 'reasync' ), ValidationFailure( node: .typeEffectSpecifiers, - message: "child 'ThrowsSpecifier' has a single keyword as its only token choice and should thus be named 'ThrowsKeyword'" + message: "child 'throwsSpecifier' has a single keyword as its only token choice and should thus be named 'throwsKeyword'" // To be consistent with AsyncSpecifier properties that can be both 'async' and 'reasync' ), @@ -248,64 +249,64 @@ class ValidateSyntaxNodes: XCTestCase { // If there are two tokens of the same kind in a node, we can't follow the naming rule without conflict ValidationFailure( node: .differentiableAttributeArguments, - message: "child 'KindSpecifierComma' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'" + message: "child 'kindSpecifierComma' has a comma keyword as its only token choice and should thus be named 'comma' or 'trailingComma'" ), ValidationFailure( node: .differentiableAttributeArguments, - message: "child 'ArgumentsComma' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'" + message: "child 'argumentsComma' has a comma keyword as its only token choice and should thus be named 'comma' or 'trailingComma'" ), ValidationFailure( node: .poundSourceLocationArguments, - message: "child 'FileColon' has a token as its only token choice and should thus be named 'Colon'" + message: "child 'fileColon' has a token as its only token choice and should thus be named 'colon'" // There are two colons in the node ), ValidationFailure( node: .poundSourceLocationArguments, - message: "child 'LineColon' has a token as its only token choice and should thus be named 'Colon'" + message: "child 'lineColon' has a token as its only token choice and should thus be named 'colon'" ), ValidationFailure( node: .regexLiteralExpr, - message: "child 'OpeningPounds' has a token as its only token choice and should thus be named 'RegexPoundDelimiter'" + message: "child 'openingPounds' has a token as its only token choice and should thus be named 'regexPoundDelimiter'" ), ValidationFailure( node: .regexLiteralExpr, - message: "child 'OpeningSlash' has a token as its only token choice and should thus be named 'RegexSlash'" + message: "child 'openingSlash' has a token as its only token choice and should thus be named 'regexSlash'" ), ValidationFailure( node: .regexLiteralExpr, - message: "child 'ClosingSlash' has a token as its only token choice and should thus be named 'RegexSlash'" + message: "child 'closingSlash' has a token as its only token choice and should thus be named 'regexSlash'" // There are the opening and closing slashes in the node ), ValidationFailure( node: .regexLiteralExpr, - message: "child 'ClosingPounds' has a token as its only token choice and should thus be named 'RegexPoundDelimiter'" + message: "child 'closingPounds' has a token as its only token choice and should thus be named 'regexPoundDelimiter'" // There are the opening and closing ExtendedRegexDelimiter in the node ), // We should explicitly mention token here because it’s not obvious that the end of a file is represented by a token - ValidationFailure(node: .sourceFile, message: "child 'EndOfFileToken' has a token as its only token choice and should thus be named 'EndOfFile'"), + ValidationFailure(node: .sourceFile, message: "child 'endOfFileToken' has a token as its only token choice and should thus be named 'endOfFile'"), ValidationFailure( node: .stringLiteralExpr, - message: "child 'OpeningPounds' has a token as its only token choice and should thus be named 'RawStringPoundDelimiter'" + message: "child 'openingPounds' has a token as its only token choice and should thus be named 'rawStringPoundDelimiter'" ), ValidationFailure( node: .stringLiteralExpr, - message: "child 'ClosingPounds' has a token as its only token choice and should thus be named 'RawStringPoundDelimiter'" + message: "child 'closingPounds' has a token as its only token choice and should thus be named 'rawStringPoundDelimiter'" ), ValidationFailure( node: .expressionSegment, - message: "child 'Pounds' has a token as its only token choice and should thus be named 'RawStringPoundDelimiter'" + message: "child 'pounds' has a token as its only token choice and should thus be named 'rawStringPoundDelimiter'" ), // MARK: Tokens that contain underscores ValidationFailure( node: .borrowExpr, - message: "child 'BorrowKeyword' has a single keyword as its only token choice and should thus be named '_borrowKeyword'" + message: "child 'borrowKeyword' has a single keyword as its only token choice and should thus be named '_borrowKeyword'" // _borrow is underscored and thus BorrowKeyword is the correct spelling ), ValidationFailure( node: .conventionWitnessMethodAttributeArguments, message: - "child 'WitnessMethodLabel' has a single keyword as its only token choice and is followed by a colon. It should thus be named 'Witness_methodLabel'" + "child 'witnessMethodLabel' has a single keyword as its only token choice and is followed by a colon. It should thus be named 'witness_methodLabel'" // Witness_method has an underscore and thus WitnessMethod is the correct spelling ), @@ -313,12 +314,12 @@ class ValidateSyntaxNodes: XCTestCase { // If the node is named the same as the token, we don't need to repeat the entire token name ValidationFailure( node: .regexLiteralExpr, - message: "child 'Regex' has a token as its only token choice and should thus be named 'RegexLiteralPattern'" + message: "child 'regex' has a token as its only token choice and should thus be named 'regexLiteralPattern'" // No point repeating the `Literal` because the node name alredy contains it ), ValidationFailure( node: .stringSegment, - message: "child 'Content' has a token as its only token choice and should thus be named 'StringSegment'" + message: "child 'content' has a token as its only token choice and should thus be named 'stringSegment'" // The node is already named `StringSegment` ), @@ -326,22 +327,22 @@ class ValidateSyntaxNodes: XCTestCase { // This is the only place where we use LeadingComma, similar to 'TrailingComma' ValidationFailure( node: .designatedType, - message: "child 'LeadingComma' has a comma keyword as its only token choice and should thus be named 'Comma' or 'TrailingComma'" + message: "child 'leadingComma' has a comma keyword as its only token choice and should thus be named 'comma' or 'trailingComma'" ), // This is similar to `TrailingComma` ValidationFailure( node: .importPathComponent, - message: "child 'TrailingPeriod' has a token as its only token choice and should thus be named 'Period'" + message: "child 'trailingPeriod' has a token as its only token choice and should thus be named 'period'" ), // `~` is the only operator that’s allowed here ValidationFailure( node: .suppressedType, - message: "child 'WithoutTilde' has a token as its only token choice and should thus be named 'Operator'" + message: "child 'withoutTilde' has a token as its only token choice and should thus be named 'operator'" ), // default is not a function argument label here but a proper keyword ValidationFailure( node: .switchDefaultLabel, - message: "child 'DefaultKeyword' has a single keyword as its only token choice and is followed by a colon. It should thus be named 'DefaultLabel'" + message: "child 'defaultKeyword' has a single keyword as its only token choice and is followed by a colon. It should thus be named 'defaultLabel'" ), ] ) @@ -362,12 +363,12 @@ class ValidateSyntaxNodes: XCTestCase { } var failureMessage: String? if child.isFollowedByColonToken(in: node) { - if !child.name.hasSuffix("Label") { - failureMessage = "child '\(child.name)' only has keywords as its token choices, is followed by a colon and should thus end with 'Label'" + if child.varOrCaseName.description != "label" && !child.varOrCaseName.description.hasSuffix("Label") { + failureMessage = "child '\(child.varOrCaseName)' only has keywords as its token choices, is followed by a colon and should thus end with 'Label'" } } else { - if !child.name.hasSuffix("Specifier") { - failureMessage = "child '\(child.name)' only has keywords as its token choices and should thus end with 'Specifier'" + if child.varOrCaseName.description != "specifier" && !child.varOrCaseName.description.hasSuffix("Specifier") { + failureMessage = "child '\(child.varOrCaseName)' only has keywords as its token choices and should thus end with 'Specifier'" } } if let failureMessage { @@ -382,40 +383,40 @@ class ValidateSyntaxNodes: XCTestCase { // MARK: Only one non-deprecated keyword ValidationFailure( node: .discardStmt, - message: "child 'DiscardKeyword' only has keywords as its token choices and should thus end with 'Specifier'" + message: "child 'discardKeyword' only has keywords as its token choices and should thus end with 'Specifier'" // DiscardKeyword can be 'discard' or '_forget' and '_forget' is deprecated ), ValidationFailure( node: .consumeExpr, - message: "child 'ConsumeKeyword' only has keywords as its token choices and should thus end with 'Specifier'" + message: "child 'consumeKeyword' only has keywords as its token choices and should thus end with 'Specifier'" // ConsumeKeyword can be 'consume' or '_move' and '_move' is deprecated ), // MARK: Conceptually a value, not a specifier ValidationFailure( node: .booleanLiteralExpr, - message: "child 'Literal' only has keywords as its token choices and should thus end with 'Specifier'" + message: "child 'literal' only has keywords as its token choices and should thus end with 'Specifier'" // TrueOrFalseKeyword would be a stupid name here ), ValidationFailure( node: .precedenceGroupAssignment, - message: "child 'Value' only has keywords as its token choices and should thus end with 'Specifier'" + message: "child 'value' only has keywords as its token choices and should thus end with 'Specifier'" ), ValidationFailure( node: .precedenceGroupAssociativity, - message: "child 'Value' only has keywords as its token choices and should thus end with 'Specifier'" + message: "child 'value' only has keywords as its token choices and should thus end with 'Specifier'" ), // MARK: Miscellaneous // 'weak' or 'unowned' are already the specifier, this is the detail in parens ValidationFailure( node: .closureCaptureSpecifier, - message: "child 'Detail' only has keywords as its token choices and should thus end with 'Specifier'" + message: "child 'detail' only has keywords as its token choices and should thus end with 'Specifier'" ), // This really is the modifier name and not a specifier ValidationFailure( node: .declModifier, - message: "child 'Name' only has keywords as its token choices and should thus end with 'Specifier'" + message: "child 'name' only has keywords as its token choices and should thus end with 'Specifier'" ), ] ) @@ -437,17 +438,17 @@ class ValidateSyntaxNodes: XCTestCase { } for (kind, children) in childrenByNodeKind where !kind.isBase && kind != .token && kind != .stringLiteralExpr { - let childNames = children.map(\.child.name) + let childNames = children.map(\.child.varOrCaseName.description) let mostCommonChildName = childNames.mostCommon! - let mostCommonChild = children.first(where: { $0.child.name == mostCommonChildName })! + let mostCommonChild = children.first(where: { $0.child.varOrCaseName.description == mostCommonChildName })! for (node, child) in children { - if child.name != mostCommonChildName { + if child.varOrCaseName.description != mostCommonChildName { failures.append( ValidationFailure( node: node.kind, message: - "child '\(child.name)' is named inconsistently with '\(mostCommonChild.node.kind.syntaxType).\(mostCommonChildName)', which has the same type ('\(kind.syntaxType)')" + "child '\(child.varOrCaseName)' is named inconsistently with '\(mostCommonChild.node.kind.syntaxType).\(mostCommonChildName)', which has the same type ('\(kind.syntaxType)')" ) ) } @@ -462,27 +463,27 @@ class ValidateSyntaxNodes: XCTestCase { ValidationFailure( node: .enumCaseElement, message: - "child 'RawValue' is named inconsistently with 'MatchingPatternConditionSyntax.Initializer', which has the same type ('InitializerClauseSyntax')" + "child 'rawValue' is named inconsistently with 'MatchingPatternConditionSyntax.initializer', which has the same type ('InitializerClauseSyntax')" ), ValidationFailure( node: .enumCaseParameter, message: - "child 'DefaultValue' is named inconsistently with 'MatchingPatternConditionSyntax.Initializer', which has the same type ('InitializerClauseSyntax')" + "child 'defaultValue' is named inconsistently with 'MatchingPatternConditionSyntax.initializer', which has the same type ('InitializerClauseSyntax')" ), ValidationFailure( node: .functionParameter, message: - "child 'DefaultValue' is named inconsistently with 'MatchingPatternConditionSyntax.Initializer', which has the same type ('InitializerClauseSyntax')" + "child 'defaultValue' is named inconsistently with 'MatchingPatternConditionSyntax.initializer', which has the same type ('InitializerClauseSyntax')" ), ValidationFailure( node: .macroDecl, message: - "child 'Definition' is named inconsistently with 'MatchingPatternConditionSyntax.Initializer', which has the same type ('InitializerClauseSyntax')" + "child 'definition' is named inconsistently with 'MatchingPatternConditionSyntax.initializer', which has the same type ('InitializerClauseSyntax')" ), // MARK: Miscellaneous ValidationFailure( node: .multipleTrailingClosureElement, - message: "child 'Closure' is named inconsistently with 'FunctionCallExprSyntax.TrailingClosure', which has the same type ('ClosureExprSyntax')" + message: "child 'closure' is named inconsistently with 'FunctionCallExprSyntax.trailingClosure', which has the same type ('ClosureExprSyntax')" ), ] ) @@ -533,11 +534,11 @@ class ValidateSyntaxNodes: XCTestCase { for node in SYNTAX_NODES.compactMap(\.layoutNode) { for child in node.nonUnexpectedChildren { - if child.name.hasSuffix("Token") { + if child.varOrCaseName.description.hasSuffix("Token") { failures.append( ValidationFailure( node: node.kind, - message: "child '\(child.name)' should not end with 'Token'" + message: "child '\(child.varOrCaseName)' should not end with 'Token'" ) ) } @@ -548,7 +549,7 @@ class ValidateSyntaxNodes: XCTestCase { failures, expectedFailures: [ // it's not obvious that the end of file is represented by a token, thus its good to highlight it in the name - ValidationFailure(node: .sourceFile, message: "child 'EndOfFileToken' should not end with 'Token'") + ValidationFailure(node: .sourceFile, message: "child 'endOfFileToken' should not end with 'Token'") ] ) } @@ -580,11 +581,11 @@ class ValidateSyntaxNodes: XCTestCase { for node in SYNTAX_NODES.compactMap(\.layoutNode) { for child in node.nonUnexpectedChildren where child.kind.isCollection { - if !child.name.hasSuffix("s") { + if !child.varOrCaseName.description.hasSuffix("s") { failures.append( ValidationFailure( node: node.kind, - message: "child '\(child.name)' is a collection and should thus be named as a plural" + message: "child '\(child.varOrCaseName)' is a collection and should thus be named as a plural" ) ) } @@ -595,7 +596,7 @@ class ValidateSyntaxNodes: XCTestCase { failures, expectedFailures: [ // The child is singular here, the path just consists of multiple components - ValidationFailure(node: .importDecl, message: "child 'Path' is a collection and should thus be named as a plural") + ValidationFailure(node: .importDecl, message: "child 'path' is a collection and should thus be named as a plural") ] ) } @@ -606,11 +607,11 @@ class ValidateSyntaxNodes: XCTestCase { for node in SYNTAX_NODES.compactMap(\.layoutNode) { for child in node.nonUnexpectedChildren { - if child.name.contains("Identifier") { + if child.varOrCaseName.description.contains("identifier") || child.varOrCaseName.description.contains("Identifier") { failures.append( ValidationFailure( node: node.kind, - message: "child '\(child.name)' should generally not contain 'Identifier'" + message: "child '\(child.varOrCaseName)' should generally not contain 'Identifier'" ) ) } @@ -621,7 +622,7 @@ class ValidateSyntaxNodes: XCTestCase { failures, expectedFailures: [ // The identifier expr / pattern nodes do actually have a child that’s the identifier - ValidationFailure(node: .identifierPattern, message: "child 'Identifier' should generally not contain 'Identifier'") + ValidationFailure(node: .identifierPattern, message: "child 'identifier' should generally not contain 'Identifier'") ] ) } @@ -658,11 +659,11 @@ class ValidateSyntaxNodes: XCTestCase { for node in SYNTAX_NODES.compactMap(\.layoutNode) { for child in node.nonUnexpectedChildren { for forbiddenSuffix in forbiddenSuffixes { - if child.name.hasSuffix(forbiddenSuffix) && child.name != forbiddenSuffix { + if child.varOrCaseName.description.hasSuffix(forbiddenSuffix) && child.varOrCaseName.description != forbiddenSuffix.withFirstCharacterLowercased { failures.append( ValidationFailure( node: node.kind, - message: "child '\(child.name)' should not end with '\(forbiddenSuffix)'" + message: "child '\(child.varOrCaseName)' should not end with '\(forbiddenSuffix)'" ) ) } @@ -675,28 +676,28 @@ class ValidateSyntaxNodes: XCTestCase { expectedFailures: [ // MARK: Adjective + Type // There’s no real better way to name these except to use an adjective followed by 'Type' - ValidationFailure(node: .attributedType, message: "child 'BaseType' should not end with 'Type'"), - ValidationFailure(node: .conformanceRequirement, message: "child 'LeftType' should not end with 'Type'"), - ValidationFailure(node: .conformanceRequirement, message: "child 'RightType' should not end with 'Type'"), - ValidationFailure(node: .extensionDecl, message: "child 'ExtendedType' should not end with 'Type'"), - ValidationFailure(node: .genericParameter, message: "child 'InheritedType' should not end with 'Type'"), - ValidationFailure(node: .implicitlyUnwrappedOptionalType, message: "child 'WrappedType' should not end with 'Type'"), - ValidationFailure(node: .memberType, message: "child 'BaseType' should not end with 'Type'"), - ValidationFailure(node: .metatypeType, message: "child 'BaseType' should not end with 'Type'"), - ValidationFailure(node: .optionalType, message: "child 'WrappedType' should not end with 'Type'"), - ValidationFailure(node: .qualifiedDeclName, message: "child 'BaseType' should not end with 'Type'"), - ValidationFailure(node: .sameTypeRequirement, message: "child 'LeftType' should not end with 'Type'"), - ValidationFailure(node: .sameTypeRequirement, message: "child 'RightType' should not end with 'Type'"), + ValidationFailure(node: .attributedType, message: "child 'baseType' should not end with 'Type'"), + ValidationFailure(node: .conformanceRequirement, message: "child 'leftType' should not end with 'Type'"), + ValidationFailure(node: .conformanceRequirement, message: "child 'rightType' should not end with 'Type'"), + ValidationFailure(node: .extensionDecl, message: "child 'extendedType' should not end with 'Type'"), + ValidationFailure(node: .genericParameter, message: "child 'inheritedType' should not end with 'Type'"), + ValidationFailure(node: .implicitlyUnwrappedOptionalType, message: "child 'wrappedType' should not end with 'Type'"), + ValidationFailure(node: .memberType, message: "child 'baseType' should not end with 'Type'"), + ValidationFailure(node: .metatypeType, message: "child 'baseType' should not end with 'Type'"), + ValidationFailure(node: .optionalType, message: "child 'wrappedType' should not end with 'Type'"), + ValidationFailure(node: .qualifiedDeclName, message: "child 'baseType' should not end with 'Type'"), + ValidationFailure(node: .sameTypeRequirement, message: "child 'leftType' should not end with 'Type'"), + ValidationFailure(node: .sameTypeRequirement, message: "child 'rightType' should not end with 'Type'"), // MARK: Adjective + Expr - ValidationFailure(node: .functionCallExpr, message: "child 'CalledExpression' should not end with 'Expression'"), - ValidationFailure(node: .subscriptCallExpr, message: "child 'CalledExpression' should not end with 'Expression'"), - ValidationFailure(node: .ternaryExpr, message: "child 'ThenExpression' should not end with 'Expression'"), - ValidationFailure(node: .ternaryExpr, message: "child 'ElseExpression' should not end with 'Expression'"), - ValidationFailure(node: .unresolvedTernaryExpr, message: "child 'ThenExpression' should not end with 'Expression'"), + ValidationFailure(node: .functionCallExpr, message: "child 'calledExpression' should not end with 'Expression'"), + ValidationFailure(node: .subscriptCallExpr, message: "child 'calledExpression' should not end with 'Expression'"), + ValidationFailure(node: .ternaryExpr, message: "child 'thenExpression' should not end with 'Expression'"), + ValidationFailure(node: .ternaryExpr, message: "child 'elseExpression' should not end with 'Expression'"), + ValidationFailure(node: .unresolvedTernaryExpr, message: "child 'thenExpression' should not end with 'Expression'"), // MARK: Other // Even though the repetition pattern is not a PatternSyntax, pattern is the correct term here - ValidationFailure(node: .packExpansionExpr, message: "child 'RepetitionPattern' should not end with 'Pattern'"), - ValidationFailure(node: .packExpansionType, message: "child 'RepetitionPattern' should not end with 'Pattern'"), + ValidationFailure(node: .packExpansionExpr, message: "child 'repetitionPattern' should not end with 'Pattern'"), + ValidationFailure(node: .packExpansionType, message: "child 'repetitionPattern' should not end with 'Pattern'"), ] ) }