Skip to content

Commit 91b7c58

Browse files
committed
refactoring type
1 parent c133e6e commit 91b7c58

5 files changed

Lines changed: 500 additions & 243 deletions

File tree

Sources/TokenVisitor/StructureProperty.swift

Lines changed: 72 additions & 4 deletions
Original file line numberDiff line numberDiff line change
@@ -28,6 +28,7 @@
2828
//
2929

3030
import Foundation
31+
import SwiftSyntax
3132

3233
/// Represents a named property within a syntax node's structure.
3334
///
@@ -41,29 +42,96 @@ import Foundation
4142
/// - Function declaration "body" property → Reference to "CodeBlockSyntax"
4243
/// - Missing optional property → nil value with just the property name
4344
package struct StructureProperty: Codable, Equatable {
45+
// MARK: - String Constants
46+
47+
/// Property name for collection element type information.
48+
private static let element: String = "Element"
49+
50+
/// Property name for collection count information.
51+
private static let count: String = "Count"
52+
53+
/// Placeholder text for missing or nil syntax elements.
54+
private static let nilValue: String = "nil"
55+
4456
/// The name of this structural property.
4557
/// Corresponds to SwiftSyntax property names like "name", "parameters", "body", etc.
46-
package let name: String
58+
private let name: String
4759

4860
/// The value of this property, if it contains terminal data.
4961
/// Present for tokens, literals, and other concrete values.
5062
/// Nil for missing optional properties.
51-
package let value: StructureValue?
63+
private let value: StructureValue?
5264

5365
/// Reference to another syntax node type, if this property contains a nested structure.
5466
/// Used when this property points to another syntax node rather than containing terminal data.
5567
/// Example: "body" property might reference "CodeBlockSyntax"
56-
package let ref: String?
68+
private let ref: String?
5769

5870
/// Creates a new StructureProperty with the specified components.
5971
///
6072
/// - Parameters:
6173
/// - name: The property name
6274
/// - value: Terminal value data, if any
6375
/// - ref: Reference to another syntax type, if any
64-
package init(name: String, value: StructureValue? = nil, ref: String? = nil) {
76+
internal init(name: String, value: StructureValue? = nil, ref: String? = nil) {
6577
self.name = name
6678
self.value = value
6779
self.ref = ref
6880
}
81+
82+
// MARK: - Convenience Initializers
83+
84+
/// Creates a StructureProperty for a missing property with a nil value indicator.
85+
///
86+
/// - Parameters:
87+
/// - name: The property name
88+
/// - nilValue: The value to display for the nil value (will be converted to string)
89+
internal init(nilValueWithName name: String) {
90+
self.init(name: name, value: StructureValue(text: Self.nilValue), ref: nil)
91+
}
92+
93+
/// Creates a StructureProperty for a token value.
94+
///
95+
/// - Parameters:
96+
/// - name: The property name
97+
/// - text: The token text
98+
/// - kind: The token kind
99+
private init(token name: String, text: String, kind: Any) {
100+
self.init(name: name, value: StructureValue(text: text, kind: "\(kind)"), ref: nil)
101+
}
102+
103+
/// Creates a StructureProperty for a syntax node reference.
104+
///
105+
/// - Parameters:
106+
/// - name: The property name
107+
/// - type: The syntax node type (will be converted to string)
108+
internal init(reference name: String, type: Any) {
109+
let typeString = "\(type)"
110+
self.init(name: name, value: StructureValue(text: typeString), ref: typeString)
111+
}
112+
113+
/// Creates a StructureProperty for a primitive value.
114+
///
115+
/// - Parameters:
116+
/// - name: The property name
117+
/// - value: The primitive value (will be converted to string)
118+
internal init(primitive name: String, value: Any) {
119+
self.init(name: name, value: StructureValue(text: "\(value)"), ref: nil)
120+
}
121+
122+
internal init(token name: String, tokenSyntax: TokenSyntax) {
123+
self.init(
124+
token: name,
125+
text: tokenSyntax.text,
126+
kind: tokenSyntax.tokenKind
127+
)
128+
}
129+
130+
internal init(collectionWithCount count: Int) {
131+
self.init(primitive: Self.count, value: count)
132+
}
133+
134+
internal init(elementWithType type: Any.Type) {
135+
self.init(primitive: Self.element, value: type)
136+
}
69137
}

Sources/TokenVisitor/StructureValue.swift

Lines changed: 10 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -59,4 +59,14 @@ package struct StructureValue: Codable, Equatable {
5959
self.text = text
6060
self.kind = kind
6161
}
62+
63+
/// Creates a new StructureValue from any value by performing string interpolation.
64+
///
65+
/// - Parameters:
66+
/// - value: Any value that will be converted to a string
67+
/// - kind: Optional kind information for additional context
68+
package init(value: Any, kind: String? = nil) {
69+
self.text = "\(value)"
70+
self.kind = kind
71+
}
6272
}
Lines changed: 199 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,199 @@
1+
//
2+
// TreeNodeProtocol+Extensions.swift
3+
// SyntaxKit
4+
//
5+
// Created by Leo Dion.
6+
// Copyright © 2025 BrightDigit.
7+
//
8+
// Permission is hereby granted, free of charge, to any person
9+
// obtaining a copy of this software and associated documentation
10+
// files (the “Software”), to deal in the Software without
11+
// restriction, including without limitation the rights to use,
12+
// copy, modify, merge, publish, distribute, sublicense, and/or
13+
// sell copies of the Software, and to permit persons to whom the
14+
// Software is furnished to do so, subject to the following
15+
// conditions:
16+
//
17+
// The above copyright notice and this permission notice shall be
18+
// included in all copies or substantial portions of the Software.
19+
//
20+
// THE SOFTWARE IS PROVIDED “AS IS”, WITHOUT WARRANTY OF ANY KIND,
21+
// EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES
22+
// OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND
23+
// NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT
24+
// HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY,
25+
// WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING
26+
// FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR
27+
// OTHER DEALINGS IN THE SOFTWARE.
28+
//
29+
30+
package import SwiftSyntax
31+
32+
extension TreeNodeProtocol {
33+
package static func parseTree(
34+
from sourceFile: SourceFileSyntax,
35+
withFileName fileName: String = .defaultFileName,
36+
showingMissingTokens: Bool = false
37+
) -> [Self] {
38+
// Use raw syntax tree without precedence folding for simplicity
39+
let syntax = Syntax(sourceFile)
40+
41+
// Create visitor to traverse AST and extract structured information
42+
let visitor = TokenVisitor<Self>(
43+
locationConverter: SourceLocationConverter(
44+
fileName: fileName,
45+
tree: sourceFile
46+
),
47+
showMissingTokens: false
48+
)
49+
50+
// Traverse the syntax tree and build our simplified representation
51+
_ = visitor.rewrite(syntax)
52+
53+
// Return the tree nodes directly
54+
return visitor.tree
55+
}
56+
}
57+
58+
// MARK: - Structure Extraction Extensions
59+
60+
extension TreeNodeProtocol {
61+
/// Appends structural information from a SwiftSyntax node to the structure array.
62+
///
63+
/// This method analyzes the node's internal structure and converts it into
64+
/// a simplified format with named properties and values, appending them to
65+
/// the structure array. It handles different structure types (layout, collection,
66+
/// choices) and processes their properties according to their specific requirements.
67+
///
68+
/// - Parameters:
69+
/// - node: The SwiftSyntax node to analyze
70+
/// - allChildren: All child nodes of the syntax node
71+
internal func appendStructure(
72+
from node: Syntax,
73+
allChildren: SyntaxChildren
74+
) {
75+
switch node.syntaxNodeType.structure {
76+
case .layout(let keyPaths):
77+
// Handle nodes with fixed structure (most syntax nodes)
78+
appendLayoutStructure(
79+
node: node,
80+
keyPaths: keyPaths,
81+
allChildren: allChildren
82+
)
83+
case .collection(let syntax):
84+
// Handle collection nodes (lists, arrays, etc.)
85+
appendCollectionStructure(
86+
elementType: syntax,
87+
allChildren: allChildren
88+
)
89+
case .choices:
90+
// Handle choice nodes (union types) - no special processing needed
91+
break
92+
}
93+
}
94+
95+
private func appendLayoutStructure(
96+
node: Syntax,
97+
keyPaths: [AnyKeyPath],
98+
allChildren: SyntaxChildren
99+
) {
100+
guard let syntaxNode = node.as(node.syntaxNodeType) else {
101+
return
102+
}
103+
104+
for keyPath in keyPaths {
105+
appendKeyPathProperty(
106+
keyPath: keyPath,
107+
syntaxNode: syntaxNode,
108+
allChildren: allChildren
109+
)
110+
}
111+
}
112+
113+
private func appendKeyPathValue(_ anyValue: Any?, withName name: String) {
114+
switch anyValue {
115+
case let value as TokenSyntax:
116+
// Handle token nodes (keywords, identifiers, operators, etc.)
117+
structure.append(
118+
StructureProperty(
119+
token: name,
120+
tokenSyntax: value
121+
)
122+
)
123+
case let value as any SyntaxProtocol:
124+
// Handle nested syntax nodes - store type reference
125+
structure.append(
126+
StructureProperty(reference: name, type: value.syntaxNodeType)
127+
)
128+
case let value?:
129+
// Handle primitive values
130+
structure.append(
131+
StructureProperty(primitive: name, value: value)
132+
)
133+
case .none:
134+
// Property exists but is nil
135+
structure.append(StructureProperty(name: name))
136+
}
137+
}
138+
139+
private func appendKeyPathProperty(
140+
keyPath: AnyKeyPath,
141+
syntaxNode: any SyntaxProtocol,
142+
allChildren: SyntaxChildren
143+
) {
144+
guard let name = String(keyPath) else {
145+
return
146+
}
147+
148+
// Check if this property has an actual child node
149+
guard allChildren.contains(where: { child in child.keyPathInParent == keyPath }) else {
150+
// Property exists but has no value - mark as nil
151+
structure.append(
152+
StructureProperty(nilValueWithName: name)
153+
)
154+
return
155+
}
156+
157+
// Extract the actual property value
158+
let keyPath = keyPath as AnyKeyPath
159+
appendKeyPathValue(syntaxNode[keyPath: keyPath], withName: name)
160+
}
161+
162+
private func appendCollectionStructure(
163+
elementType: Any.Type,
164+
allChildren: SyntaxChildren
165+
) {
166+
// Mark as collection type
167+
type = .collection
168+
169+
// Add element type information
170+
structure.append(
171+
StructureProperty(elementWithType: elementType)
172+
)
173+
174+
// Add count information
175+
structure.append(
176+
StructureProperty(collectionWithCount: allChildren.count)
177+
)
178+
}
179+
}
180+
181+
extension String {
182+
fileprivate static let defaultFileName = ""
183+
184+
fileprivate init?(_ keyPath: AnyKeyPath) {
185+
let keyPathString = String(describing: keyPath)
186+
187+
// Extract the last component after the last dot
188+
if let lastDotIndex = keyPathString.lastIndex(of: ".") {
189+
let afterDot = keyPathString[keyPathString.index(after: lastDotIndex)...]
190+
self = String(afterDot)
191+
} else {
192+
// If no dots found, use the whole string
193+
guard !keyPathString.isEmpty else {
194+
return nil
195+
}
196+
self = keyPathString
197+
}
198+
}
199+
}

0 commit comments

Comments
 (0)