The package is designed to gather information from Swift source files and compile this information into concrete objects with
strongly typed properties containing descriptions of found symbols.
In other words, if you have a source code file like
// MyClass.swift
/// My class does nothing.
open class MyClass {}
— Synopsis will give you structurized information that there’s a class, it’s open and named MyClass, with no methods nor properties,
and the class is documented as My class does nothing. Also, it has no parents.
Synopsis structure is your starting point. This structure provides you with an init(files:) initializer that accepts a list of file URLs
of your *.swift source code files.
let mySwiftFiles: [URL] = getFiles()
let synopsis = Synopsis(files: mySwiftFiles)
Initialized Synopsis structure has properties classes, structures, protocols, enums and functions containing descirpitons
of found classes, structs, protocols, enums and high-level free functions respectively. You may also examine parsingErrors property
with a list of problems occured during the compilation process.
struct Synopsis {
let classes: [ClassDescription]
let structures: [StructDescription]
let protocols: [ProtocolDescription]
let enums: [EnumDescription]
let functions: [FunctionDescription]
let parsingErrors: [SynopsisError]
}
Classes, structs and protocols
Meta-information about found classes, structs and protocols is organized as ClassDescription, StructDescription
or ProtocolDescription structs respectively. Each of these implements an Extensible protocol.
protocol Extensible: Equatable, CustomDebugStringConvertible {
var comment: String?
var annotations: [Annotation]
var declaration: Declaration
var accessibility: Accessibility
var name: String
var inheritedTypes: [String]
var properties: [PropertyDescription]
var methods: [MethodDescription]
var verse: String // this one is special
}
Extensibles (read like «classes», «structs» or «protocols») include
comment — an optional documentation above the extensible.
annotations — a list of Annotation instances parsed from the comment; see Annotation for more details.
declaration — an information, where this current extensible could be found (file, line number, column number etc.); see Declaration for more details.
accessibility — an enum of private, internal, public and open.
name — an extensible name.
inheritedTypes — a list of all parents, if any.
properties — a list of all properties; see Property for more details.
methods — a list of methods, including initializers; see Methods and functions for more details.
There’s also a special computed property verse: String, which allows to obtain the Extensible as a source code.
This is a convenient way of composing new utility classes, see Code generation, templates and versing for more information.
All extensibles support Equatable and CustomDebugStringConvertible protocols, and extend Sequence with
subscript(name:) and contains(name:) methods.
struct EnumDescription: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let declaration: Declaration
let accessibility: Accessibility
let name: String
let inheritedTypes: [String]
let cases: [EnumCase] // !!! enum cases !!!
let properties: [PropertyDescription]
let methods: [MethodDescription]
var verse: String
}
Enum descriptions contain almost the same information as the extensibles, but also include a list of cases.
Enum cases
struct EnumCase: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let name: String
let defaultValue: String? // everything after "=", e.g. case firstName = "first_name"
let declaration: Declaration
var verse: String
}
All enum cases have String names, and declarations. They may also have documentation (with annotations) and optional defaultValue: String?.
You should know, that defaultValue is a raw text, which may contain symbols like quotes.
class FunctionDescription: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let accessibility: Accessibility
let name: String
let arguments: [ArgumentDescription]
let returnType: TypeDescription?
let declaration: Declaration
let kind: Kind // see below
let body: String?
var verse: String
enum Kind {
case free
case class
case static
case instance
}
}
Synopsis assumes that method is a function subclass with a couple additional features.
Methods also have a computed property isInitializer: Bool.
class MethodDescription: FunctionDescription {
var isInitializer: Bool {
return name.hasPrefix("init(")
}
}
// literally no more reasonable code
While most of the FunctionDescription properties are self-explanatory, some of them have their own quirks and tricky details behind.
For instance, method names must contain round brackets () and are actually a kind of a signature without types, e.g. myFunction(argument:count:).
func myFunction(arg argument: String) -> Int {}
// this function is named "myFunction(arg:)"
Function kind could only be free, while methods could have a class, static or instance kind.
Methods inside protocols have the same set of properties, but contain no body.
The body itself is a text inside curly brackets {...}, but without brackets.
func topLevelFunction() {
}
// this function body is equal to "\n"
Arguments
struct ArgumentDescription: Equatable, CustomDebugStringConvertible {
let name: String
let bodyName: String
let type: TypeDescription
let defaultValue: String?
let annotations: [Annotation]
let comment: String?
var verse: String
}
Function and method arguments all have external and internal names, a type, an optional defaultValue, own optional documentation and annotations.
External name is an argument name when the function is called. Internal bodyName is used insibe function body. Both are mandatory, though they could be equal.
Properties are represented with a PropertyDescription struct.
struct PropertyDescription: Equatable, CustomDebugStringConvertible {
let comment: String?
let annotations: [Annotation]
let accessibility: Accessibility
let constant: Bool // is it "let"? If not, it's "var"
let name: String
let type: TypeDescription
let defaultValue: String? // literally everything after "=", if there is a "="
let declaration: Declaration
let kind: Kind // see below
let body: String? // literally everything between curly brackets, but without brackets
var verse: String
enum Kind {
case class
case static
case instance
}
}
Properties could have documentation and annotations. All properties have own kind of class, static or instance.
All properties have names, constant boolean flag, accessibility, type (see TypeDescription), a raw defaultValue: String?
and a declaration: Declaration.
Computed properties could also have a body, like functions. The body itself is a text inside curly brackets {...},
but without brackets.
Annotations
struct Annotation: Equatable, CustomDebugStringConvertible {
let name: String
let value: String?
}
Extensibles, enums, functions, methods and properties are all allowed to have documentation.
Synopsis parses documentation in order to gather special annotation elements with important meta-information.
These annotations resemble Java annotations, but lack their compile-time checks.
All annotations are required to have a name. Annotations can also contain an optional String value.
Annotations are recognized by the @ symbol, for instance:
/// @model
class Model {}
N.B. Documentation comment syntax is inherited from the Swift compiler, and for now supports block comments and triple slash comments.
Method or function arguments usually contain documentation in the nearby inline comments, see below.
Use line breaks or semicolons ; to divide separate annotations:
While some of these cases are self-explanatory, others need additional clarification.
integer type for now has a limitation, as it represents all Int types like Int16, Int32 etc. This means Synopsis won’t let you determine the Int size.
optional type contains a wrapped TypeDescription for the actual value type. Same happens for arrays, maps and generics.
All object types except for Data, Date, NSData and NSDate are represented with an object(name: String) case. So, while CGRect is a struct, Synopsis will still thinks it is an object("CGRect").
Declaration
struct Declaration: Equatable {
public let filePath: URL
public let rawText: String
public let offset: Int
public let lineNumber: Int
public let columnNumber: Int
}
Classes, structs, protocols, properties, methods etc. — almost all detected source code elements have a declaration: Declaration property.
Declaration structure encapsulates several properties:
filePath — a URL to the end file, where the source code element was detected;
rawText — a raw line, which was parsed in order to detect source code element;
offset — a numer of symbols from the beginning of file to the detected source code element;
lineNumber — self-explanatory;
columnNumber — self-explanatory; starts from 1.
Code generation, templates and versing
Each source code element provides a computed String property verse, which allows to obtain this element’s source code.
This source code is composed programmatically, thus it may differ from the by-hand implementation.
This allows to generate new source code by composing, e.g, ClassDescrption instances by hand.
Though, each ClassDescription instance requires a Declaration, which contains a filePath, rawText, offset and other properties yet to be defined, because such source code hasn’t been generated yet.
This is why ClassDescription and others provide you with a template(...) constructor, which replaces declaration with a special mock object.
Please, consider reviewing Tests/SynopsisTests/Versing test cases in order to get familiar with the concept.
Use spm_resolve.command to load all dependencies and spm_generate_xcodeproj.command to assemble an Xcode project file.
Also, ensure Xcode targets macOS when running tests.
Description
The package is designed to gather information from Swift source files and compile this information into concrete objects with strongly typed properties containing descriptions of found symbols.
In other words, if you have a source code file like
— Synopsis will give you structurized information that there’s a
class
, it’sopen
and namedMyClass
, with no methods nor properties, and the class is documented asMy class does nothing
. Also, it has no parents.Installation
Swift Package Manager dependency
Usage
Synopsis struct
Synopsis
structure is your starting point. This structure provides you with aninit(files:)
initializer that accepts a list of file URLs of your*.swift
source code files.Initialized
Synopsis
structure has propertiesclasses
,structures
,protocols
,enums
andfunctions
containing descirpitons of found classes, structs, protocols, enums and high-level free functions respectively. You may also examineparsingErrors
property with a list of problems occured during the compilation process.Classes, structs and protocols
Meta-information about found classes, structs and protocols is organized as
ClassDescription
,StructDescription
orProtocolDescription
structs respectively. Each of these implements anExtensible
protocol.Extensible
Extensibles (read like «classes», «structs» or «protocols») include
comment
— an optional documentation above the extensible.annotations
— a list ofAnnotation
instances parsed from thecomment
; see Annotation for more details.declaration
— an information, where this current extensible could be found (file, line number, column number etc.); see Declaration for more details.accessibility
— anenum
ofprivate
,internal
,public
andopen
.name
— an extensible name.inheritedTypes
— a list of all parents, if any.properties
— a list of all properties; see Property for more details.methods
— a list of methods, including initializers; see Methods and functions for more details.There’s also a special computed property
verse: String
, which allows to obtain theExtensible
as a source code. This is a convenient way of composing new utility classes, see Code generation, templates and versing for more information.All extensibles support
Equatable
andCustomDebugStringConvertible
protocols, and extendSequence
withsubscript(name:)
andcontains(name:)
methods.Enums
Enum descriptions contain almost the same information as the extensibles, but also include a list of cases.
Enum cases
All enum cases have
String
names, and declarations. They may also have documentation (with annotations) and optionaldefaultValue: String?
.You should know, that
defaultValue
is a raw text, which may contain symbols like quotes.Methods and functions
Synopsis assumes that method is a function subclass with a couple additional features.
All functions have
private
,internal
,public
oropen
);ArgumentDescription
, see below);TypeDescription
, see below);Declaration
, see below);Methods also have a computed property
isInitializer: Bool
.While most of the
FunctionDescription
properties are self-explanatory, some of them have their own quirks and tricky details behind. For instance, method names must contain round brackets()
and are actually a kind of a signature without types, e.g.myFunction(argument:count:)
.Function
kind
could only befree
, while methods could have aclass
,static
orinstance
kind.Methods inside protocols have the same set of properties, but contain no body. The body itself is a text inside curly brackets
{...}
, but without brackets.Arguments
Function and method arguments all have external and internal names, a type, an optional
defaultValue
, own optional documentation and annotations.External
name
is an argument name when the function is called. InternalbodyName
is used insibe function body. Both are mandatory, though they could be equal.Argument type is described below, see TypeDescription.
Properties
Properties are represented with a
PropertyDescription
struct.Properties could have documentation and annotations. All properties have own
kind
ofclass
,static
orinstance
. All properties have names,constant
boolean flag, accessibility, type (see TypeDescription), a rawdefaultValue: String?
and adeclaration: Declaration
.Computed properties could also have a
body
, like functions. The body itself is a text inside curly brackets{...}
, but without brackets.Annotations
Extensibles, enums, functions, methods and properties are all allowed to have documentation.
Synopsis parses documentation in order to gather special annotation elements with important meta-information. These annotations resemble Java annotations, but lack their compile-time checks.
All annotations are required to have a name. Annotations can also contain an optional
String
value.Annotations are recognized by the
@
symbol, for instance:Use line breaks or semicolons
;
to divide separate annotations:Keep annotated function or method arguments on their own separate lines for readability:
Though it is not prohibited to have annotations above arguments:
Types
Property types, argument types, function return types are represented with a
TypeDescription
enum with cases:boolean
integer
floatingPoint
doublePrecision
string
date
data
optional(wrapped: TypeDescription)
object(name: String)
array(element: TypeDescription)
map(key: TypeDescription, value: TypeDescription)
generic(name: String, constraints: [TypeDescription])
While some of these cases are self-explanatory, others need additional clarification.
integer
type for now has a limitation, as it represents allInt
types likeInt16
,Int32
etc. This means Synopsis won’t let you determine theInt
size.optional
type contains a wrappedTypeDescription
for the actual value type. Same happens for arrays, maps and generics.All object types except for
Data
,Date
,NSData
andNSDate
are represented with anobject(name: String)
case. So, whileCGRect
is a struct,Synopsis
will still thinks it is anobject("CGRect")
.Declaration
Classes, structs, protocols, properties, methods etc. — almost all detected source code elements have a
declaration: Declaration
property.Declaration
structure encapsulates several properties:Code generation, templates and versing
Each source code element provides a computed
String
propertyverse
, which allows to obtain this element’s source code.This source code is composed programmatically, thus it may differ from the by-hand implementation.
This allows to generate new source code by composing, e.g,
ClassDescrption
instances by hand.Though, each
ClassDescription
instance requires aDeclaration
, which contains afilePath
,rawText
,offset
and other properties yet to be defined, because such source code hasn’t been generated yet.This is why
ClassDescription
and others provide you with atemplate(...)
constructor, which replaces declaration with a special mock object.Please, consider reviewing
Tests/SynopsisTests/Versing
test cases in order to get familiar with the concept.Running tests
Use
spm_resolve.command
to load all dependencies andspm_generate_xcodeproj.command
to assemble an Xcode project file. Also, ensure Xcode targets macOS when running tests.