Skip to content

Instantly share code, notes, and snippets.

@Saik0s
Created February 8, 2025 02:46
Show Gist options
  • Save Saik0s/a6f09e3f1cdeb6424ce343609a8fd581 to your computer and use it in GitHub Desktop.
Save Saik0s/a6f09e3f1cdeb6424ce343609a8fd581 to your computer and use it in GitHub Desktop.
sourcery template for generating a code index file from swift source files
---
description: Code index maps all types, methods and properties across the project files. Mirrors folder structure to give instant high-level view of codebase architecture - perfect for grasping project scope and organization at a glance.
globs: **/*.{swift,md}
---
Format:
./Directory/
struct TypeName: Protocols
- property: Type
- method(param: Type) -> ReturnType
Excludes:
- Private/internal implementation details
- Boilerplate code
- Types marked with // sourcery: skipIndex
<%
/* ---------------------------------------------------------------------
1. Directory Tree Data Structure
--------------------------------------------------------------------- */
class DirectoryNode {
var name: String
var children: [String: DirectoryNode] = [:]
var types: [Type] = []
init(name: String) {
self.name = name
}
func insertType(_ type: Type, pathComponents: [String]) {
guard !pathComponents.isEmpty else {
self.types.append(type)
return
}
let head = pathComponents[0]
let tail = Array(pathComponents.dropFirst())
if children[head] == nil {
children[head] = DirectoryNode(name: head)
}
children[head]!.insertType(type, pathComponents: tail)
}
func sortRecursively() {
types.sort { $0.name < $1.name }
for child in children.values {
child.sortRecursively()
}
}
}
/* ---------------------------------------------------------------------
2. Filtering & Building the Directory
--------------------------------------------------------------------- */
// Skip any type that has `// sourcery: skipIndex`
func shouldIncludeType(_ t: Type) -> Bool {
if t.annotations["skipIndex"] != nil {
return false
}
return true
}
func buildDirectoryTree() -> DirectoryNode {
let root = DirectoryNode(name: "ROOT")
for type in types.all {
guard shouldIncludeType(type) else { continue }
// 'directory' is optional string
let dir = type.directory ?? ""
let dirPath = dir.trimmingCharacters(in: CharacterSet(charactersIn: "/"))
let components = dirPath.split(separator: "/").map(String.init)
root.insertType(type, pathComponents: components)
}
root.sortRecursively()
return root
}
/* ---------------------------------------------------------------------
3. Access Level & Boilerplate Checks
--------------------------------------------------------------------- */
// method.accessLevel is a String (or String?).
func methodAccessIsPrivate(_ m: SourceryRuntime.Method) -> Bool {
let level = m.accessLevel
// Compare to string "private"/"fileprivate"
return (level == "private" || level == "fileprivate")
}
// Variables generally have `readAccess` & `writeAccess` as Strings.
func variableAccessIsPrivate(_ v: Variable) -> Bool {
// If either is private/fileprivate, skip
let read = v.readAccess
let write = v.writeAccess
if read == "private" || read == "fileprivate" { return true }
if write == "private" || write == "fileprivate" { return true }
return false
}
// Remove standard Swift boilerplate
func isBoilerplateMethod(_ method: SourceryRuntime.Method) -> Bool {
if method.callName == "init" { return true }
if method.callName == "==" { return true }
if method.callName == "hash" { return true }
return false
}
func isSwiftUIBody(_ varName: String, _ type: Type) -> Bool {
let inheritsView = type.inheritedTypes.contains("View")
let implementsView = type.implements.values.contains { $0.name == "View" }
return (inheritsView || implementsView) && (varName == "body")
}
/* ---------------------------------------------------------------------
4. Rendering
--------------------------------------------------------------------- */
func renderDirectoryNode(_ node: DirectoryNode, level: Int = 0) -> String {
let indent = String(repeating: " ", count: max(0, level - 1))
var output = ""
// Handle directory name display with merging single children
if node.name != "ROOT" {
var currentNode = node
var pathComponents = [node.name]
// Keep merging while there's exactly one child and no types
while currentNode.children.count == 1 && currentNode.types.isEmpty {
let childNode = currentNode.children.values.first!
pathComponents.append(childNode.name)
currentNode = childNode
}
if level > 0 {
output += "\(indent)./\(pathComponents.joined(separator: "/"))/\n"
}
// Render types for the last node in chain
for t in currentNode.types {
output += renderType(t, level: level + 1)
}
// Render remaining children
let sortedChildNames = currentNode.children.keys.sorted()
for childName in sortedChildNames {
if let childNode = currentNode.children[childName] {
output += renderDirectoryNode(childNode, level: level + 1)
}
}
return output
}
// Root node handling remains the same
for t in node.types {
output += renderType(t, level: level + 1)
}
let sortedChildNames = node.children.keys.sorted()
for childName in sortedChildNames {
if let childNode = node.children[childName] {
output += renderDirectoryNode(childNode, level: level)
}
}
return output.replacingOccurrences(of: "UnknownTypeSoAddTypeAttributionToVariable", with: "")
}
func renderType(_ t: Type, level: Int) -> String {
var result = ""
let indent = String(repeating: " ", count: max(0, level - 1))
// documentation is an array of strings
if !t.documentation.isEmpty {
for docLine in t.documentation {
result += "\(indent)/// \(docLine)\n"
}
}
result += "\(indent)\(t.kind) \(t.name)"
// Inherits & Implements
let filteredInherits = t.inheritedTypes.filter { !["Codable", "Equatable", "Identifiable", "Hashable", "Encodable", "Decodable"].contains($0) }
if !filteredInherits.isEmpty {
result += ": \(filteredInherits.joined(separator: ", "))"
}
let filteredImplements = t.implements.filter { !["Codable", "Equatable", "Identifiable", "Hashable", "Encodable", "Decodable"].contains($0.value.name) }
if !filteredImplements.isEmpty {
let addedColon = !filteredInherits.isEmpty
let implemented = filteredImplements.map { $0.value.name }.joined(separator: ", ")
result += (addedColon ? ", " : ": ") + implemented
}
result += "\n"
// Properties
let filteredVars = t.variables.filter {
!variableAccessIsPrivate($0) && !isSwiftUIBody($0.name, t) && !$0.isStatic
}
if !filteredVars.isEmpty {
for v in filteredVars {
result += "\(indent) - \(v.name): \(v.typeName.name)\n"
}
}
// Methods
let filteredMethods = t.methods.filter {
!methodAccessIsPrivate($0) && !isBoilerplateMethod($0) && !$0.isStatic
}
if !filteredMethods.isEmpty {
if !filteredVars.isEmpty {
result += "\n"
}
for m in filteredMethods {
result += "\(indent) - \(methodSignature(m))\n"
}
}
// Static Variables
let filteredStaticVars = t.staticVariables.filter {
!variableAccessIsPrivate($0) && !isSwiftUIBody($0.name, t)
}
if !filteredStaticVars.isEmpty {
for sv in filteredStaticVars {
result += "\(indent) + \(sv.name): \(sv.typeName.name)\n"
}
}
// Static Methods
let filteredStaticMethods = t.staticMethods.filter {
!methodAccessIsPrivate($0) && !isBoilerplateMethod($0) && !$0.isStatic
}
if !filteredStaticMethods.isEmpty {
if !filteredVars.isEmpty {
result += "\n"
}
for sm in filteredStaticMethods {
result += "\(indent) + \(methodSignature(sm))\n"
}
}
result += "\n"
return result
}
func methodSignature(_ method: SourceryRuntime.Method) -> String {
let throwMods = [
method.`throws` ? "throws" : "",
method.`rethrows` ? "rethrows" : ""
].filter { !$0.isEmpty }.joined(separator: " ")
let returnName = method.returnTypeName.name
let returnSegment = returnName.isEmpty ? "" : " -> \(returnName)"
return "\(method.name)" +
(throwMods.isEmpty ? "" : " \(throwMods)") +
returnSegment
}
%>
<%
/* ---------------------------------------------------------------------
5. MAIN TEMPLATE ENTRY
--------------------------------------------------------------------- */
let root = buildDirectoryTree()
-%>
<%= renderDirectoryNode(root) %>
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment