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’sopenand 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
Synopsisstructure is your starting point. This structure provides you with aninit(files:)initializer that accepts a list of file URLs of your*.swiftsource code files.Initialized
Synopsisstructure has propertiesclasses,structures,protocols,enumsandfunctionscontaining descirpitons of found classes, structs, protocols, enums and high-level free functions respectively. You may also examineparsingErrorsproperty 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,StructDescriptionorProtocolDescriptionstructs respectively. Each of these implements anExtensibleprotocol.Extensible
Extensibles (read like «classes», «structs» or «protocols») include
comment— an optional documentation above the extensible.annotations— a list ofAnnotationinstances 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— anenumofprivate,internal,publicandopen.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 theExtensibleas 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
EquatableandCustomDebugStringConvertibleprotocols, and extendSequencewithsubscript(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
Stringnames, and declarations. They may also have documentation (with annotations) and optionaldefaultValue: String?.You should know, that
defaultValueis 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,publicoropen);ArgumentDescription, see below);TypeDescription, see below);Declaration, see below);Methods also have a computed property
isInitializer: Bool.While most of the
FunctionDescriptionproperties 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
kindcould only befree, while methods could have aclass,staticorinstancekind.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
nameis an argument name when the function is called. InternalbodyNameis 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
PropertyDescriptionstruct.Properties could have documentation and annotations. All properties have own
kindofclass,staticorinstance. All properties have names,constantboolean 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
Stringvalue.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
TypeDescriptionenum with cases:booleanintegerfloatingPointdoublePrecisionstringdatedataoptional(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.
integertype for now has a limitation, as it represents allInttypes likeInt16,Int32etc. This means Synopsis won’t let you determine theIntsize.optionaltype contains a wrappedTypeDescriptionfor the actual value type. Same happens for arrays, maps and generics.All object types except for
Data,Date,NSDataandNSDateare represented with anobject(name: String)case. So, whileCGRectis a struct,Synopsiswill still thinks it is anobject("CGRect").Declaration
Classes, structs, protocols, properties, methods etc. — almost all detected source code elements have a
declaration: Declarationproperty.Declarationstructure encapsulates several properties:Code generation, templates and versing
Each source code element provides a computed
Stringpropertyverse, 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,
ClassDescrptioninstances by hand.Though, each
ClassDescriptioninstance requires aDeclaration, which contains afilePath,rawText,offsetand other properties yet to be defined, because such source code hasn’t been generated yet.This is why
ClassDescriptionand others provide you with atemplate(...)constructor, which replaces declaration with a special mock object.Please, consider reviewing
Tests/SynopsisTests/Versingtest cases in order to get familiar with the concept.Running tests
Use
spm_resolve.commandto load all dependencies andspm_generate_xcodeproj.commandto assemble an Xcode project file. Also, ensure Xcode targets macOS when running tests.