{"id":20492545,"url":"https://github.com/redmadrobot/synopsis","last_synced_at":"2025-04-13T17:02:36.435Z","repository":{"id":63920388,"uuid":"112665649","full_name":"RedMadRobot/synopsis","owner":"RedMadRobot","description":"Swift source code scanner.","archived":false,"fork":false,"pushed_at":"2018-04-25T18:59:45.000Z","size":74,"stargazers_count":13,"open_issues_count":0,"forks_count":1,"subscribers_count":8,"default_branch":"master","last_synced_at":"2025-04-13T17:02:30.639Z","etag":null,"topics":["code-generation","ios","macos","sourcekit","swift","template"],"latest_commit_sha":null,"homepage":"","language":"Swift","has_issues":true,"has_wiki":null,"has_pages":null,"mirror_url":null,"source_name":null,"license":"mit","status":null,"scm":"git","pull_requests_enabled":true,"icon_url":"https://github.com/RedMadRobot.png","metadata":{"files":{"readme":"README.md","changelog":null,"contributing":null,"funding":null,"license":"LICENSE","code_of_conduct":null,"threat_model":null,"audit":null,"citation":null,"codeowners":null,"security":null,"support":null}},"created_at":"2017-11-30T22:08:03.000Z","updated_at":"2021-07-28T21:09:11.000Z","dependencies_parsed_at":"2023-01-14T14:00:45.546Z","dependency_job_id":null,"html_url":"https://github.com/RedMadRobot/synopsis","commit_stats":null,"previous_names":[],"tags_count":3,"template":false,"template_full_name":null,"repository_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fsynopsis","tags_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fsynopsis/tags","releases_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fsynopsis/releases","manifests_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories/RedMadRobot%2Fsynopsis/manifests","owner_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners/RedMadRobot","download_url":"https://codeload.github.com/RedMadRobot/synopsis/tar.gz/refs/heads/master","host":{"name":"GitHub","url":"https://github.com","kind":"github","repositories_count":248750074,"owners_count":21155685,"icon_url":"https://github.com/github.png","version":null,"created_at":"2022-05-30T11:31:42.601Z","updated_at":"2022-07-04T15:15:14.044Z","host_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub","repositories_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repositories","repository_names_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/repository_names","owners_url":"https://repos.ecosyste.ms/api/v1/hosts/GitHub/owners"}},"keywords":["code-generation","ios","macos","sourcekit","swift","template"],"created_at":"2024-11-15T17:29:33.747Z","updated_at":"2025-04-13T17:02:36.341Z","avatar_url":"https://github.com/RedMadRobot.png","language":"Swift","funding_links":[],"categories":[],"sub_categories":[],"readme":"![](main-illustration.png)\n## Description\n\nThe package is designed to gather information from Swift source files and compile this information into concrete objects with\nstrongly typed properties containing descriptions of found symbols.\n\nIn other words, if you have a source code file like\n```swift\n// MyClass.swift\n\n/// My class does nothing.\nopen class MyClass {}\n```\n— **Synopsis** will give you structurized information that there's a `class`, it's `open` and named `MyClass`, with no methods nor properties,\nand the class is documented as `My class does nothing`. Also, it has no parents.\n\n## Installation\n### Swift Package Manager dependency\n\n```swift\nPackage.Dependency.package(\n    url: \"https://github.com/RedMadRobot/synopsis\",\n    from: \"1.0.0\"\n)\n```\n\n## Usage\n\n* [Synopsis struct](#synopsis-struct)\n    - [Classes, structs and protocols](#classdescription)\n    - [Enums](#enums)\n    - [Methods and functions](#functions)\n    - [Properties](#properties)\n    - [Annotations](#annotation)\n    - [Property types, argument types, return types](#types)\n    - [Declaration](#declarations)\n* [Code generation, templates and versing](#versing)\n* [Running tests](#tests)\n\n\u003ca name=\"synopsis-struct\" /\u003e\n\n### Synopsis struct\n\n`Synopsis` structure is your starting point. This structure provides you with an `init(files:)` initializer that accepts a list of file URLs\nof your `*.swift` source code files.\n\n```swift\nlet mySwiftFiles: [URL] = getFiles()\n\nlet synopsis = Synopsis(files: mySwiftFiles)\n```\n\nInitialized `Synopsis` structure has properties `classes`, `structures`, `protocols`, `enums` and `functions` containing descirpitons\nof found classes, structs, protocols, enums and high-level free functions respectively. You may also examine `parsingErrors` property\nwith a list of problems occured during the compilation process.\n\n```swift\nstruct Synopsis {\n    let classes:        [ClassDescription]\n    let structures:     [StructDescription]\n    let protocols:      [ProtocolDescription]\n    let enums:          [EnumDescription]\n    let functions:      [FunctionDescription]\n    let parsingErrors:  [SynopsisError]\n}\n```\n\n\u003ca name=\"classdescription\" /\u003e\n\n### Classes, structs and protocols\n\nMeta-information about found classes, structs and protocols is organized as `ClassDescription`, `StructDescription`\nor `ProtocolDescription` structs respectively. Each of these implements an `Extensible` protocol.\n\n```swift\nstruct ClassDescription:    Extensible {}\nstruct StructDescription:   Extensible {}\nstruct ProtocolDescription: Extensible {}\n```\n\n\u003ca name=\"extensible\" /\u003e\n\n#### Extensible\n\n```swift\nprotocol Extensible: Equatable, CustomDebugStringConvertible {\n    var comment:        String?\n    var annotations:    [Annotation]\n    var declaration:    Declaration\n    var accessibility:  Accessibility\n    var name:           String\n    var inheritedTypes: [String]\n    var properties:     [PropertyDescription]\n    var methods:        [MethodDescription]\n\n    var verse: String // this one is special\n}\n```\n\nExtensibles (read like «classes», «structs» or «protocols») include\n\n* `comment` — an optional documentation above the extensible.\n* `annotations` — a list of `Annotation` instances parsed from the `comment`; see [Annotation](#annotation) for more details.\n* `declaration` — an information, where this current extensible could be found (file, line number, column number etc.); see [Declaration](#declarations) for more details.\n* `accessibility` — an `enum` of `private`, `internal`, `public` and `open`.\n* `name` — an extensible name.\n* `inheritedTypes` — a list of all parents, if any.\n* `properties` — a list of all properties; see [Property](#properties) for more details.\n* `methods` — a list of methods, including initializers; see [Methods and functions](#functions) for more details.\n\nThere's also a special computed property `verse: String`, which allows to obtain the `Extensible` as a source code.\nThis is a convenient way of composing new utility classes, see [Code generation, templates and versing](#versing) for more information.\n\nAll extensibles support `Equatable` and `CustomDebugStringConvertible` protocols, and extend `Sequence` with\n`subscript(name:)` and `contains(name:)` methods.\n\n```swift\nextension Sequence where Iterator.Element: Extensible {\n    subscript(name: String) -\u003e Iterator.Element?\n    func contains(name: String) -\u003e Bool\n}\n```\n\n\u003ca name=\"enums\" /\u003e\n\n### Enums\n\n```swift\nstruct EnumDescription: Equatable, CustomDebugStringConvertible {\n    let comment:        String?\n    let annotations:    [Annotation]\n    let declaration:    Declaration\n    let accessibility:  Accessibility\n    let name:           String\n    let inheritedTypes: [String]\n    let cases:          [EnumCase] // !!! enum cases !!!\n    let properties:     [PropertyDescription]\n    let methods:        [MethodDescription]\n\n    var verse: String\n}\n```\n\nEnum descriptions contain almost the same information as the extensibles, but also include a list of cases.\n\n\u003ca name=\"cases\" /\u003e\n\n#### Enum cases\n\n```swift\nstruct EnumCase: Equatable, CustomDebugStringConvertible {\n    let comment:        String?\n    let annotations:    [Annotation]\n    let name:           String\n    let defaultValue:   String? // everything after \"=\", e.g. case firstName = \"first_name\"\n    let declaration:    Declaration\n    \n    var verse:          String\n}\n```\n\nAll enum cases have `String` names, and declarations. They may also have documentation (with [annotations](#annotation)) and optional `defaultValue: String?`.\n\nYou should know, that `defaultValue` is a raw text, which may contain symbols like quotes.\n\n```swift\nenum CodingKeys {\n    case firstName = \"first_name\" // defaultValue == \"\\\"first_name\\\"\"\n}\n```\n\n\u003ca name=\"functions\" /\u003e\n\n### Methods and functions\n\n```swift\nclass FunctionDescription: Equatable, CustomDebugStringConvertible {\n    let comment:        String?\n    let annotations:    [Annotation]\n    let accessibility:  Accessibility\n    let name:           String\n    let arguments:      [ArgumentDescription]\n    let returnType:     TypeDescription?\n    let declaration:    Declaration\n    let kind:           Kind // see below\n    let body:           String?\n    \n    var verse: String\n    \n    enum Kind {\n        case free\n        case class\n        case static\n        case instance\n    }\n}\n```\n\n**Synopsis** assumes that method is a function subclass with a couple additional features.\n\nAll functions have\n\n* optional documentation;\n* [annotations](#annotation);\n* accessibility (`private`, `internal`, `public` or `open`);\n* name;\n* list of arguments (of type `ArgumentDescription`, [see below](#arguments));\n* optional return type (of type `TypeDescription`, [see below](#types));\n* a declaration (of type `Declaration`, [see below](#declarations));\n* kind;\n* optional body.\n\nMethods also have a computed property `isInitializer: Bool`.\n\n```swift\nclass MethodDescription: FunctionDescription {\n    var isInitializer: Bool {\n        return name.hasPrefix(\"init(\")\n    }\n}\n// literally no more reasonable code\n```\n\nWhile most of the `FunctionDescription` properties are self-explanatory, some of them have their own quirks and tricky details behind.\nFor instance, method names must contain round brackets `()` and are actually a kind of a signature without types, e.g. `myFunction(argument:count:)`.\n\n```swift\nfunc myFunction(arg argument: String) -\u003e Int {}\n// this function is named \"myFunction(arg:)\"\n```\n\nFunction `kind` could only be `free`, while methods could have a `class`, `static` or `instance` kind.\n\nMethods inside protocols have the same set of properties, but contain no body.\nThe body itself is a text inside curly brackets `{...}`, but without brackets.\n\n```swift\nfunc topLevelFunction() {\n}\n// this function body is equal to \"\\n\"\n```\n\n\u003ca name=\"arguments\" /\u003e\n\n#### Arguments\n\n```swift\nstruct ArgumentDescription: Equatable, CustomDebugStringConvertible {\n    let name:           String\n    let bodyName:       String\n    let type:           TypeDescription\n    let defaultValue:   String?\n    let annotations:    [Annotation]\n    let comment:        String?\n\n    var verse: String\n}\n```\n\nFunction and method arguments all have external and internal names, a type, an optional `defaultValue`, own optional documentation and [annotations](#annotation).\n\nExternal `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.\n\nArgument type is described below, see [TypeDescription](#types).\n\n\u003ca name=\"properties\" /\u003e\n\n### Properties\n\nProperties are represented with a `PropertyDescription` struct.\n\n```swift\nstruct PropertyDescription: Equatable, CustomDebugStringConvertible {\n    let comment:        String?\n    let annotations:    [Annotation]\n    let accessibility:  Accessibility\n    let constant:       Bool                // is it \"let\"? If not, it's \"var\"\n    let name:           String\n    let type:           TypeDescription\n    let defaultValue:   String?             // literally everything after \"=\", if there is a \"=\"\n    let declaration:    Declaration\n    let kind:           Kind                // see below\n    let body:           String?             // literally everything between curly brackets, but without brackets\n\n    var verse: String\n    \n    enum Kind {\n        case class\n        case static\n        case instance\n    }\n}\n```\n\nProperties could have documentation and [annotations](#annotation). All properties have own `kind` of `class`, `static` or `instance`.\nAll properties have names, `constant` boolean flag, accessibility, type (see [TypeDescription](#types)), a raw `defaultValue: String?`\nand a `declaration: Declaration`.\n\nComputed properties could also have a `body`, like functions. The body itself is a text inside curly brackets `{...}`,\nbut without brackets.\n\n\u003ca name=\"annotation\" /\u003e\n\n### Annotations\n\n```swift\nstruct Annotation: Equatable, CustomDebugStringConvertible {\n    let name: String\n    let value: String?\n}\n```\n\nExtensibles, enums, functions, methods and properties are all allowed to have documentation.\n\n**Synopsis** parses documentation in order to gather special annotation elements with important meta-information.\nThese annotations resemble Java annotations, but lack their compile-time checks.\n\nAll annotations are required to have a name. Annotations can also contain an optional `String` value.\n\nAnnotations are recognized by the `@` symbol, for instance:\n\n```swift\n/// @model\nclass Model {}\n```\n\n\u003e N.B. Documentation comment syntax is inherited from the Swift compiler, and for now supports block comments and triple slash comments.\n\u003e Method or function arguments usually contain documentation in the nearby inline comments, see below.\n\nUse line breaks or semicolons `;` to divide separate annotations:\n\n```swift\n/**\n @annotation1\n @annotation2; @annotation3\n @annotation4 value1\n @annotation5 value2; @annotation5 value3\n @anontation6; @annotation7 value4\n */\n```\n\nKeep annotated function or method arguments on their own separate lines for readability:\n\n```swift\nfunc doSomething(\n    with argument: String,    // @annotation1\n    or argument2: Int,        /* @annotation2 value1; @annotation3 value2 */\n    finally argument3: Double // @annotation4; annotation5 value3\n) -\u003e Int\n```\n\nThough it is not prohibited to have annotations above arguments:\n\n```swift\nfunc doSomething(\n    // @annotation1\n    with argument: String,\n    /* @annotation2 value1; @annotation3 value2 */\n    or argument2: Int,\n    // @annotation4; annotation5 value3\n    finally argument3: Double\n) -\u003e Int\n```\n\n\u003ca name=\"types\" /\u003e\n\n### Types\n\nProperty types, argument types, function return types are represented with a `TypeDescription` enum with cases:\n\n* `boolean`\n* `integer`\n* `floatingPoint`\n* `doublePrecision`\n* `string`\n* `date`\n* `data`\n* `optional(wrapped: TypeDescription)`\n* `object(name: String)`\n* `array(element: TypeDescription)`\n* `map(key: TypeDescription, value: TypeDescription)`\n* `generic(name: String, constraints: [TypeDescription])`\n\nWhile some of these cases are self-explanatory, others need additional clarification.\n\n`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.\n\n`optional` type contains a wrapped `TypeDescription` for the actual value type. Same happens for arrays, maps and generics.\n\nAll 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\")`.\n\n\u003ca name=\"declarations\" /\u003e\n\n### Declaration\n\n```swift\nstruct Declaration: Equatable {\n    public let filePath:        URL\n    public let rawText:         String\n    public let offset:          Int\n    public let lineNumber:      Int\n    public let columnNumber:    Int\n}\n```\n\nClasses, structs, protocols, properties, methods etc. — almost all detected source code elements have a `declaration: Declaration` property.\n\n`Declaration` structure encapsulates several properties:\n\n* filePath — a URL to the end file, where the source code element was detected;\n* rawText — a raw line, which was parsed in order to detect source code element;\n* offset — a numer of symbols from the beginning of file to the detected source code element;\n* lineNumber — self-explanatory;\n* columnNumber — self-explanatory; starts from 1.\n\n\u003ca name=\"versing\" /\u003e\n\n### Code generation, templates and versing\n\nEach source code element provides a computed `String` property `verse`, which allows to obtain this element's source code.\n\nThis source code is composed programmatically, thus it may differ from the by-hand implementation.\n\nThis allows to generate new source code by composing, e.g, `ClassDescrption` instances by hand.\n\nThough, 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.\n\nThis is why `ClassDescription` and others provide you with a `template(...)` constructor, which replaces declaration with a special mock object.\n\nPlease, consider reviewing `Tests/SynopsisTests/Versing` test cases in order to get familiar with the concept.\n\n```swift\nfunc testVerse_fullyPacked_returnsAsExpected() {\n    let enumDescription = EnumDescription.template(\n        comment: \"Docs\",\n        accessibility: Accessibility.`private`,\n        name: \"MyEnum\",\n        inheritedTypes: [\"String\"],\n        cases: [\n            EnumCase.template(comment: \"First\", name: \"firstName\", defaultValue: \"\\\"first_name\\\"\"),\n            EnumCase.template(comment: \"Second\", name: \"lastName\", defaultValue: \"\\\"last_name\\\"\"),\n        ],\n        properties: [],\n        methods: []\n    )\n    \n    let expectedVerse = \"\"\"\n    /// Docs\n    private enum MyEnum: String {\n        /// First\n        case firstName = \"first_name\"\n\n        /// Second\n        case lastName = \"last_name\"\n    }\n\n    \"\"\"\n    \n    XCTAssertEqual(enumDescription.verse, expectedVerse)\n}\n```\n\n\u003ca name=\"tests\" /\u003e\n\n### Running tests\n\nUse `spm_resolve.command` to load all dependencies and `spm_generate_xcodeproj.command` to assemble an Xcode project file.\nAlso, ensure Xcode targets macOS when running tests.\n","project_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredmadrobot%2Fsynopsis","html_url":"https://awesome.ecosyste.ms/projects/github.com%2Fredmadrobot%2Fsynopsis","lists_url":"https://awesome.ecosyste.ms/api/v1/projects/github.com%2Fredmadrobot%2Fsynopsis/lists"}