Coder Social home page Coder Social logo

krzysztofzablocki / sourcery Goto Github PK

View Code? Open in Web Editor NEW
7.5K 87.0 603.0 57.2 MB

Meta-programming for Swift, stop writing boilerplate code.

Home Page: http://merowing.info

License: MIT License

Ruby 0.85% Swift 97.07% Shell 0.06% Objective-C 0.31% JavaScript 1.58% EJS 0.09% Dockerfile 0.05%
metaprogramming swift codegen codegenerator ios templates code-generation

sourcery's Introduction

macOS 13 ubuntu x86_64

docs Version License Platform

In-Depth Sourcery guide is covered as part of my SwiftyStack engineering course.

Sourcery Pro provides a powerful Stencil editor and extends Xcode with the ability to handle live AST templates: available on Mac App Store

Overview.mp4

Learn more about Sourcery Pro

Sourcery is a code generator for Swift language, built on top of Apple's own SwiftSyntax. It extends the language abstractions to allow you to generate boilerplate code automatically.

It's used in over 40,000 projects on both iOS and macOS and it powers some of the most popular and critically-acclaimed apps you have used (including Airbnb, Bumble, New York Times). Its massive community adoption was one of the factors that pushed Apple to implement derived Equality and automatic Codable conformance. Sourcery is maintained by a growing community of contributors.

Try Sourcery for your next project or add it to an existing one -- you'll save a lot of time and be happy you did!

TL;DR

Sourcery allows you to get rid of repetitive code and create better architecture and developer workflows. An example might be implementing Mocks for all your protocols, without Sourcery you will need to write hundreds lines of code per each protocol like this:

class MyProtocolMock: MyProtocol {

    //MARK: - sayHelloWith
    var sayHelloWithNameCallsCount = 0
    var sayHelloWithNameCalled: Bool {
        return sayHelloWithNameCallsCount > 0
    }
    var sayHelloWithNameReceivedName: String?
    var sayHelloWithNameReceivedInvocations: [String] = []
    var sayHelloWithNameClosure: ((String) -> Void)?

    func sayHelloWith(name: String) {
        sayHelloWithNameCallsCount += 1
        sayHelloWithNameReceivedName = name
        sayHelloWithNameReceivedInvocations.append(name)
        sayHelloWithNameClosure?(name)
    }

}

and with Sourcery ?

extension MyProtocol: AutoMockable {}

Sourcery removes the need to write any of the mocks code, how many protocols do you have in your project? Imagine how much time you'll save, using Sourcery will also make every single mock consistent and if you refactor or add properties, the mock code will be automatically updated for you, eliminating possible human errors.

Sourcery can be applied to arbitrary problems across your codebase, if you can describe an algorithm to another human, you can automate it using Sourcery.

Most common uses are:

But how about more specific use-cases, like automatically generating all the UI for your app BetaSetting? you can use Sourcery for that too

Once you start writing your own template and learn the power of Sourcery you won't be able to live without it.

How To Get Started

There are plenty of tutorials for different uses of Sourcery, and you can always ask for help in our Swift Forum Category.

Quick Mocking Intro & Getting Started Video

You can also watch this quick getting started and intro to mocking video by Inside iOS Dev:

Watch the video

Installation

  • Binary form

    Download the latest release with the prebuilt binary from release tab. Unzip the archive into the desired destination and run bin/sourcery

  • Homebrew

    brew install sourcery

  • CocoaPods

    Add pod 'Sourcery' to your Podfile and run pod update Sourcery. This will download the latest release binary and will put it in your project's CocoaPods path so you will run it with $PODS_ROOT/Sourcery/bin/sourcery

    If you only want to install the sourcery binary, you may want to use the CLI-Only subspec: pod 'Sourcery', :subspecs => ['CLI-Only'].

  • Mint

    mint run krzysztofzablocki/Sourcery

  • Building from Source

    Download the latest release source code from the release tab or clone the repository and build Sourcery manually.

    • Building with Swift Package Manager

      Run swift build -c release in the root folder and then copy .build/release/sourcery to your desired destination.

      Note: JS templates are not supported when building with SPM yet.

    • Building with Xcode

      Run xcodebuild -scheme sourcery -destination generic/platform=macOS -archivePath sourcery.xcarchive archive and export the binary from the archive.

  • SPM (for plugin use only) Add the package dependency to your Package.swift manifest from version 1.8.3.

.package(url: "https://github.com/krzysztofzablocki/Sourcery.git", from: "1.8.3")
  • pre-commit Add the dependency to .pre-commit-config.yaml.
- repo: https://github.com/krzysztofzablocki/Sourcery
  rev: 1.9.1
  hooks:
  - id: sourcery

Documentation

Full documentation for the latest release is available here.

Linux Support

Linux support is described on this page.

Usage

Running the executable

Sourcery is a command line tool; you can either run it manually or in a custom build phase using the following command:

$ ./bin/sourcery --sources <sources path> --templates <templates path> --output <output path>

Note: this command differs depending on how you installed Sourcery (see Installation)

Swift Package command

Sourcery can now be used as a Swift package command plugin. In order to do this, the package must be added as a dependency to your Swift package or Xcode project (see Installation above).

To provide a configuration for the plugin to use, place a .sourcery.yml file at the root of the target's directory (in the sources folder rather than the root of the package).

Running from the command line

To verify the plugin can be found by SwiftPM, use:

$ swift package plugin --list

To run the code generator, you need to allow changes to the project with the --allow-writing-to-package-directory flag:

$ swift package --allow-writing-to-package-directory sourcery-command

Running in Xcode

Inside a project/package that uses this command plugin, right-click the project and select "SourceryCommand" from the "SourceryPlugins" menu group.

⚠️ Note that this is only available from Xcode 14 onwards.

Command line options

  • --sources - Path to a source swift files or directories. You can provide multiple paths using multiple --sources option.
  • --templates - Path to templates. File or Directory. You can provide multiple paths using multiple --templates options.
  • --force-parse - File extensions of Sourcery generated file you want to parse. You can provide multiple extension using multiple --force-parse options. (i.e. file.toparse.swift will be parsed even if generated by Sourcery if --force-parse toparse). Useful when trying to implement a multiple phases generation. --force-parse can also be used to process within a sourcery annotation. For example to process code within sourcery:inline:auto:Type.AutoCodable annotation you can use --force-parse AutoCodable
  • --output [default: current path] - Path to output. File or Directory.
  • --config [default: current path] - Path to config file. File or Directory. See Configuration file.
  • --args - Additional arguments to pass to templates. Each argument can have an explicit value or will have implicit true value. Arguments should be separated with , without spaces (i.e. --args arg1=value,arg2). Arguments are accessible in templates via argument.name
  • --watch [default: false] - Watch both code and template folders for changes and regenerate automatically.
  • --verbose [default: false] - Turn on verbose logging
  • --quiet [default: false] - Turn off any logging, only emit errors
  • --disableCache [default: false] - Turn off caching of parsed data
  • --prune [default: false] - Prune empty generated files
  • --version - Display the current version of Sourcery
  • --help - Display help information
  • --cacheBasePath - Base path to the cache directory. Can be overriden by the config file.
  • --buildPath - Path to directory used when building from .swifttemplate files. This defaults to system temp directory
  • --hideVersionHeader [default: false] - Stop adding the Sourcery version to the generated files headers.

Configuration file

Instead of CLI arguments, you can use a .sourcery.yml configuration file:

sources:
  - <sources path>
  - <sources path>
templates:
  - <templates path>
  - <templates path>
forceParse:
  - <string value>
  - <string value>
output:
  <output path>
args:
  <name>: <value>

Read more about this configuration file here.

Issues

If you get an unverified developer warning when using binary zip distribution try: xattr -dr com.apple.quarantine Sourcery-1.1.1

Contributing

Contributions to Sourcery are welcomed and encouraged!

It is easy to get involved. Please see the Contributing guide for more details.

A list of contributors is available through GitHub.

To clarify what is expected of our community, Sourcery has adopted the code of conduct defined by the Contributor Covenant. This document is used across many open source communities, and articulates my values well. For more, see the Code of Conduct.

Sponsoring

If you'd like to support Sourcery development you can do so through GitHub Sponsors or Open Collective, it's highly appreciated 🙇‍

If you are a company and would like to sponsor the project directly and get it's logo here, you can contact me directly

Sponsors

Bumble Inc

Airbnb Engineering

License

Sourcery is available under the MIT license. See LICENSE for more information.

Attributions

This tool is powered by

Thank you! to:

  • Mariusz Ostrowski for creating the logo.
  • Artsy Eidolon team, because we use their codebase as a stub data for performance testing the parser.
  • Olivier Halligon for showing me his setup scripts for CLI tools which are powering our rakefile.
  • JP Simard for creating SourceKitten that originally powered Sourcery and was instrumental in making this project happen.

Other Libraries / Tools

If you want to generate code for asset related data like .xib, .storyboards etc. use SwiftGen. SwiftGen and Sourcery are complementary tools.

Make sure to check my other libraries and tools, especially:

  • KZPlayground - Powerful playgrounds for Swift and Objective-C
  • KZFileWatchers - Daemon for observing local and remote file changes, used for building other developer tools (Sourcery uses it)

You can follow me on Twitter for news/updates about other projects I am creating.

sourcery's People

Contributors

albsala avatar alisoftware avatar antondomashnev avatar art-divin avatar artyom-razinov avatar asifmujteba avatar bejar37 avatar cyberbeni avatar davidbertet avatar dependabot[bot] avatar divinedominion avatar erichoracek avatar hartbit avatar hemet avatar igor-savelev-bumble avatar ilyapuchka avatar jimmya avatar joannis avatar krzysztofzablocki avatar kzaher avatar liamnichols avatar liquidsoul avatar lukkas avatar lutzifer avatar obbut avatar paul1893 avatar polpielladev avatar stephanecopin avatar till0xff avatar vknabel avatar

Stargazers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

Watchers

 avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar  avatar

sourcery's Issues

Adding support for external source, e.g. Foundation or 3rd Party libraries

Feature request: access Foundation types, standard library types, and even third party package types within stencils. This would make it a lot easier to generate boilerplate extensions. My current workaround is copy/pasting the swift interface to a file in the folder but not adding it to Xcode (hacky but works).

Not sure how we'd go about achieving this, could be a limit of SourceKit – maybe we could replicate the copy-paste hack by specifying a target, generating the interface, writing it to a temporary file and pointing SourceKit's type parser at it (which seems to happen at the Sema phase, pre-AST).

Ideas for better name that avoids linguistic ableism

Congrats on a super neat project! I wish only that it had a better name.

Ableist language is any word or phrase that intentionally or inadvertently targets an individual with a disability... Examples of ableist language include “crazy,” “insane,” “lame,” “dumb,” “retarded,” “blind,” “deaf,” “idiot,” “imbecile,” “invalid (noun),” “maniac,” “nuts,” “psycho,” “spaz.”

Each of these words, when used flippantly, can be extremely insulting to individuals who find themselves with physical (“lame,” “invalid,” “dumb”) or mental (“crazy,” “retarded,” “psycho”) disabilities.

(via here)

Naming is a Hard Problem™ and I'm guilty of using this language too. I encourage you to consider a new name while this project is still new. Thanks!

Edit: From your own Code of Conduct:

Examples of behavior that contributes to creating a positive environment include:

Using welcoming and inclusive language
...
Showing empathy towards other community members

Relative paths don't work

This doesn't work

$ Pods/Insanity/bin/insanity InsanityDemo Insanity/Templates/allEnums.stencil Insanity/Generated
'InsanityDemo' does not exist or is not readable.

But this works

$ Pods/Insanity/bin/insanity "$PWD"/InsanityDemo "$PWD"/Insanity/Templates/allEnums.stencil "$PWD"/Insanity/Generated
Scanning sources...
Found 3 types
Loading templates...
Loaded 1 templates
Generating code...

`types.implementing` with an extension?

Hi,
I'm using a phantom protocol to guide my code generation, and I noticed something interesting.

I have a template that looks like this:

{% for types.implementing.MyProtocol %}
// Generate some stuff here...
{% endfor %}

This works fine when I declare conformance like this:

struct IntBag: MyProtocol {
   let int: Int
}

but not like this:

struct IntBag {
   let int: Int
}

extension IntBag: MyProtocol { }

Is there a particular reason why the second example isn't picked up?

Xcode 8 Source Code Extension

How about we add a Xcode 8 Source Code Extension, we could use Sourcery to scan the code in the given file and feed the extension the meta type information.

That would mean that Xcode would now support context-aware snippets, which would make it significantly more powerful, even AppCode doesn't support those.

Also, I think it would increase the number of potential users of the Sourcery, because if we have that feature, even people that don't want to do code generation in their build phases can leverage and contribute to the tool.

Crash on multibyte enum case

Arguments

/Users/toshi0383/github/Sourcery/test/ /Users/toshi0383/github/Sourcery/test/autoequatable.swift.template /Users/toshi0383/github/Sourcery/out/

source: test/Sample.swift

protocol AutoEquatable {}
enum JapaneseEnum {
    case アイウエオ
}
struct Cat: AutoEquatable {
    let name: String
    let age: Int
    let flag: Bool
}
struct Dog: AutoEquatable {
    let name: String
    let age: Int
    let flag: Bool
}

template: test/autoequatable.swift.template

{% for type in types.implementing.AutoEquatable %}
extension {{ type.name }}: Equatable {}

func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool {
    {% for variable in type.storedVariables %} if lhs.{{ variable.name }} != rhs.{{ variable.name }} { return false }
    {% endfor %}
    return true
}
{% endfor %}

crash
xcode-crash

Looks like it's inside SourceKitten.
SwiftLint doesn't crash on same code. I don't know why.

Adding support for SPM

Is the plan for consumers of Sourcery to download a built-binary, and then target that at their configuration, or is the plan for people to mostly compile sourcery while patching in their changes?

If the plan is mostly expecting people to compile Sourcery, then I think it would be great if we had Carthage/SPM support. I don't think it's overly political to suggest that there are strong feelings about CocoaPods and its weaknesses/issues.

I don't know if Swift Package Manager is setup to deal with a target like Sourcery, but even if it's only preliminary or partial support, I think it would be useful to have that integrated. Since Sourcery is a tool for building great Swift Apps, I think it would be a good community citizen if it also had SPM support.

Generic arguments

Hi, is there a way to get generic arguments of structs, classes ...?

Detect typealiases

Will it be helpful if parser will be able to detect typealiases and maybe store them in a property of a Type just like contained types and global aliases in types.typealiasescollection?

Type aliases should be scoped to the containing type

With current implementation type aliases with the same name defined in different types will override each other. That breaks the usual case of defining type alias to conform to protocol.

struct Foo {}
enum FooEnum: RawRepresentable {
  typealias RawValue = Foo
  var rawValue: RawValue { ... }
}

struct Bar {}
enum BarEnum: RawRepresentable {
  typealias RawValue = Bar
  var rawValue: RawValue { ... }
}

In this case type of rawValue property of FooEnum will be detected incorrectly as Bar as RawValue = Bar will override RawValue = Foo

Type `inheritedTypes` should flatten types hierarchy

Currently we only put into inheritedTypes types that we directly inherit from or implement. Shouldn't we flatten types hierarchy so that if type that implements a protocol (or inherits from class) that inherits from another protocol we can check in the template if type implements both of them?

For example with such code:

protocol AutoEquatable: Equatable {}
class BaseClass: AutoEquatable {}
class SubClass: BaseClass {}

in the template {% types.implementing.Equatable %}will not give us any type when it should give us both BaseClass and SubClass. {% types.implementing.AutoEquatable %} will only give us BaseClass, not SubClass.

Implementation should be trivial I think.

Handle closure types

As mentioned in #72 we may add support for closures, to detect variables/method parameters types being closures, detect number and types of their arguments (may correlate with #48). Will require to strip unneeded spaces as SourceKit does not do that for type names

Should we flatten variables?

If a type inherits from another type, or implements given protocol, should the variables be available in the given type itself?
e.g.

protocol Something {
  var typeName: String { get }
}

struct Foo: Something {
}
  1. Should we list typeName in Foo? if so should we add alternative variable so people can choose between inherited or direct variables?

  2. Also same question can be asked in regards to annotations, if the protocol specified type level annotation, should we flatten that into Foo ?

@ilyapuchka what do you think?

Person type not being found

Just started trying Sourcery and it's awesome!
I hope I'm not doing something wrong, but for some reason a type (class, struct or enum) with name Person won't be found.

What version of Swift are you using?
3.0

What did you do?
Created a very basic swift script file.
Literally only this:

class Person {}

template:

// Found {{ types.all.count }} Types
// Found {{ types.classes.all.count }} classes
// Found {{ types.structs.all.count }} structs
// {% for type in types.all %} {{ type.name }}
// {% endfor %}

What did you expect to see?

// Found 1 Types
// Found 1 classes
// Found 0 structs
//  Person
// 

What did you see instead?

// Found 0 Types
// Found 0 classes
// Found 0 structs
// 

If I rename the class/struct/enum to Person1 for example, the output is correct.

Detect open variables, methods and classes

As in Swift not everything is automatically overridable there should be a way to check for that in templates to not try to override these methods/variables or subclass non-open classes. Can be implemented by adding isOpen property to methods, properties and classes.

@krzysztofzablocki should we also add a dedicated type Class and move there properties like supertype, inherits and this new property as they are not applicable for structs, enums and protocols?

Handle tuples as variable types

As it's possible to compare tuples of Equatable types, but it's not possible to extend tuple types with protocols, we should support them to be able to use them in equatable template. For example it could be used like { % if variable.isTuple and variable.tuple.allTypes.implements.AutoEquatable % }. Also we may support tuple elements names.

Inherits vs Implements

Currently implements is just an alias for inherits. But wouldn't it align more with language concepts of implementing some protocol and inheriting from some type if inherits will only contain inherited type (which can be only one and only for classes) and implements will contain all implemented protocols? Or is there any particular reason why you decided not to distinguish these types? Is it just a SourceKitten limitation or something else?

Also there must be cases for templates when it's needed to distinguish inherited type and implemented protocols, though I can not come up with an example right now.

The second thing is that maybe it will make sense not to put in inherited types a raw type name for enums, as it is not a case of inheritance per se. And there is already rawType for that.

What do you think @krzysztofzablocki ? Will be glad to make a pr for that.

Sourcery as a framework

Hi,
here at Bending Spoons we decided to leverage Sourcery as a way to generate some code for our applications.

To integrate, in the best possible way, Sourcery with our tooling we would like to embed it in our internal swift CLI.

We are in the process of understanding what is the best way to do so before start coding. We think that the best way would be to have Sourcery as a framework, similarly to Carthage and Swiftlint. Both those projects have a CLI and a Kit and all the functionalities are exposed through API.

What do you think about this approach? Is it something that you would considering doing? Do you suggest a different way to approach this problem?
Cheers

cc @lucaquerella

Bug in "type.implements"?

I'm trying the feature implemented in #42 (as requested in #41)

This is my code

struct T1: Equatable {
  static func == (l: T1, r: T1) -> Bool {
    return true
  }
}

struct T: AutoEquatable {
  let a: Int
}

struct T2: AutoEquatable {
  let t: T
  let t1: T1
}

with this condition, the T1 struct is ignored

{% if variable.type.implements.AutoEquatable or variable.type.implements.Equatable %}

while with this one, it works:

{% if variable.type.implements.AutoEquatable or variable.type.inheritedTypes.Equatable %}

If I print the types, inheritedTypes values have the expected content (so AutoEquatable for T and T2, and Equatable for T1). It seems a bug in the implements "function".

If this is the desired behaviour, I think it is confusing (right now, maybe there is a good reason I don't see :) )

Add more Stencil nodes (to be able to do stuff like enum.allValues)

As discussed in Slack, we definitely need to move the Stencil nodes added in SwiftGen to make them available into Stencil proper, or at least Insanity should merge them.
This issue is just to keep track of progress on that matter.

For now, there's too many stuff that are not possible with Insanity due to Stencil limitations in nodes.


For example, I wanted to add a template that would generate allValues for each enum.
Currently, this is not possible, because:

  1. We need to generate allValues only if the enum has no case with an associated value: if it has at least one case with an AV that won't make sense to generate allValues. But there's currently no way in Stencil to test if "any item in that array has that property":
  • enum.cases.hasAssociatedValue doesn't work because enum.cases is an array,
  • there is no map or reduce or any Node in Stencil
  • We could do it with a for loop and {% set hasAV %}1{% endset %} if one of the iterated case hasAssociatedValue so that we could then {% if hasAV %} afterwards, but the set node isn't part of Stencil yet either
  1. We need to generate allValues only if the enum isn't generic, as static var for generic types are not yet supported. Currently the Stencil context doesn't expose if the type is generic (there's no field in the Stencil Context telling that type.isGeneric for example)
  • For our purposes that's not that problematic because a generic enum like Result<T> will likely use T as a type of one of its associated types anyway, so if we manage to solve 1 that won't be a blocker for our allValues case, but still.

For lack of a better solution, this works for enums with no associated values, but omit cases with associated values when there is one (instead of not generating the allValues property at all. But it still generates an invalid code for generic enums anyway.

{% for enum in types.enums %}
extension {{ enum.name }} {
  static var allValues: [{{enum.name}}] = [
    {% for case in enum.cases %}{% if not case.hasAssociatedValue %}.{{case.name}}, {%endif%}{% endfor %}
  ]
}
{% endfor %}

Workflows & Best Practices - Example Use Cases

It looks like there are several possible ways to use Sourcery, and I think it would make sense to streamline those into example workflows.

Some Example Use Cases:

  • Inline -- The most common mode people would use Sourcery. It runs as a build phase script with real Swift project and generates any boilerplate code based on user provided templates. The recently landed comment-attribute system plays nicely in this model.
  • Daemon mode -- Using daemon offers the user to run code against existing source and experiment with the templating system. This is by far the best way to write new templates, regardless of which use-case you are trying to cover.
  • Xcode Extension -- In this mode, I believe we're using Sourcery as a really powerful snippet engine. The idea is to replace stuff, once, by scanning some of the code, and then generating some subset of methods based on the piece selected. The recently landed comment-attribute system plays nicely in this model.
  • Documentation -- In this mode, the user uses Sourcery as a parse tree that then helps the user extract interesting things, and generate external resources based on their code. We're using Sourcery as a parsing + template engine. The output of Sourcery in this use case does not serve as code within the App.
  • Separated Specs & Code -- This is sort of a combination of Inline and Documentation, but with some extra features. Specifically, the target customer is making some API/Framework layer. They define their type hierarchy in a directory we can call "Specifications". These specifications get parsed and generate the output code in a wholly separate directory. The output directory is the real Framework/App Xcodeproject, and the user mixes handwritten code with the generated code at this level. The purpose of having a separate input directory is we can have bigger transformations on the code. In issue #20 I suggested a syntax for Attributes that was not dependent on comments. In-code attributes have the benefit of being available to auto-complete, and so the API Generator project could type-check the attributes themselves. Benefits of that include early alerts when combining incompatible attributes.

I think all these use cases are all valid targets for Sourcery, and I think having some example projects & how-to tutorials for each of these case would be valuable. I think this ticket touches a bit on issue #10 -- if we share templates with the community, I think we should show the user how to use them, and for which purpose a given template is targeted.

Add `implements` and `inherits` properties to type

As now we have a separation between implementing and inheriting types collections in generator we may as well add corresponding properties to Type model.
Then inheritedTypes (should be renamed to based probably?) becomes analogous to types.based, implements to types.implementing and inherits to types.inheriting.

With that instead of variable.type.inheritedTypes.MyProtocol it will be possible to do variable.type.implements.MyProtocol which just reads better. Also it will be more consistent with mentioned separation of inheriting and implementing types in generator.

Can't see enums implementing a protocol?

I'm trying to write a template to deserialize enums, and trying to generate code for all enums that implement a phantom protocol, AutoEnumDecodable. However, it seems like types.implementing.Protocol doesn't work for enum types?

{% for type in types.implementing.AutoEnumDecodable %}
extension {{type.name}}: JsonDecodable {
    static func parse(decoder: CupertinoDecoder) {
        //...
    }
}
{% endfor %}

Enums that implement AutoEnumEncodable aren't generating anything.
BTW, I'm really enjoying using this library so far!

Don't hardcode the path in the usage help

When installed via Pods, the usage still mention the .app:

$ Pods/Insanity/bin/sourcery --help
Usage:

    $ Insanity.app/Contents/MacOS/Sourcery <source> <templates> <output>

Installed via CocoaPods, Sourcery 0.2.0

Templates currently can't express all logic

I have been trying to do things like this:

{% for type in types.implementing.AutoEquatable %}
extension {{ type.name }}: Equatable {}

func == (lhs: {{ type.name }}, rhs: {{ type.name }}) -> Bool {
    {% for variable in type.storedVariables %}
    {% if variable.type implements Equatable or variable.type implements AutoEquatable %}
    if lhs.{{ variable.name }} != rhs.{{ variable.name }} { return false }
    {% endif %}
    {% endfor %}
    return true
}
{% endfor %}

or this:

{% for enum in types.enums where enum.name beginswith Gallery %}
extension {{ enum.name }} {
  static var count: Int { return {{ enum.cases.count }} }
}
{% endfor %}

and while operators like where ... beginswith and if ... implements are certainly possible to add to the template language, won't this project be hindered by the fact that the template language is not Turing-complete? Would it be possible to implement a library to build a Swift AST from Swift?

Feature Request: Daemon Mode watch Sources

Currently, the daemon mode documented & built-in to Sourcery targets observing the single template for changes and regenerating on that, while maintaining AST cache.
It would also be useful to be able to watch the source-code directory, and regenerate AST and code automatically as you change your input files.

Check variable implements a protocol

Hi!
First of all great work!
I had the same idea from months but I've never found the time to implement it. Very great work :)

I'm trying to understand how to check if a variable (of a struct) implements a protocol but I can't find a way.
Is it possible? it could be that I'm missing something though (I just started to try the project)

Stops discovering types after encountering generated files

I have discovered that Sourcery seems to stop discovering types after it encounters source files generated by Sourcery.

Given the following file list, sourcery only discovers types in files files A and B.

/source

  • A.swift
  • B.swift
  • /generated (folder containing files generated by sourcery)
  • H.swift

This is easily worked around by making sure that your generated files are not located under the input directory.

Generate more errors to ease writing templates

@kzaher suggested a valid point,
if someone is doing things like types.implementing.AutoEquatable it would be useful to throw error if AutoEquatable isn't in our database of known types, so that people understand why it's happening.

If we are doing that then we can as well do following:

  • If someone references Class in implementing inform them it's used via inheriting or base, same for mismatches in inheriting
  • If someone references a type we don't know about via implementing inheriting let them know about base

Default values for variables

It would be great to have the ability to access a default value assigned to a variable.

I would like to contribute if possible, just need some opinions and directions! :)

👍

How should we approach sharing generic templates with the community?

We can expect more useful templates to pop up in future and we even might to create few to drive and test some new features in generator or even just for documentation purposes. It would be cool if there will be a way to share these templates in some known place.

So what then will be the strategy to distribute and share such templates?
Subfolder in root of this repo? Or maybe through separate repository? Via subspec (I don't remember accurately if CocoaPods supports repos without targets)? Any other options?

Crash when `()` is used as Void

The following code makes Sourcery crash

struct AnAction {
  typealias LoadingPayload = Request
  typealias CompletedPayload = CGRect
  typealias FailedPayload = ()
}

Workaround: Change () with Void

The code crashes here

            guard let alias = extract(tokens[index + 1], contents: contents),
                let type = extract(tokens[index + 2], contents: contents) else {
                    continue
            }

In parseTypealiases

It basically tries to access to a out of range index

Sourcery version: 0.5.0

'YES' does not exist or is not readable.

Hi
I encountered a runtime crash caused by force unwrapping, so I'm trying to debug it.

When I run Sourcery.xcworkspace from Xcode without any arguments, it exits with message below.

'YES' does not exist or is not readable.

Even if I pass the required arguments correctly, it still complains.

Unknown Arguments: -DnouMtSdbiNmcResg YES

Arguments:

    �[34msource�[0m - Path to a source swift files
    �[34mtemplates�[0m - Path to templates. File or Directory≥
    �[34moutput�[0m - Path to output. File or Directory.

Options:
    �[34m--watch�[0m [default: false] - Watch template for changes and regenerate as needed. Only works with specific template path (not directory).
    �[34m--verbose�[0m [default: false] - Turn on verbose logging for ignored entities

Xcode Version 8.2 (8C38)

Any idea?

Annotations should be available for Enum.Case.

Hi everyone!

As of version 0.4.3, annotations are available for types and variables, but not for cases of enum's.

I think it would be very nice to have access to annotations for enum cases.

For example, I'm trying to wrap localized strings in some custom MyLocalizable structs and starting from

enum Localizable {
    enum Category {
        /// sourcery comment = "Some comment"
        case someKey = "Default value"
    }
}

I would like to generate

struct Localizable {
    struct Category {
        static let someKey = MyLocalizable(key: "Localizable.Category.someKey", default: "Default value", comment: "Some comment")
    }
}

Please note that the enum is not part of the project. It is used only by Sourcery to generate code.
MyLocalizable has a string var that returns the localized string and I can then simply use Localizable.Category.someKey.string. where I need to. The Localizable.strings files can easily be generated from Sourcery too.

With the current version, the best I can do is to use a struct instead of an enum:

struct Localizable {
    struct Category {
        /// sourcery comment = "Some comment"
        /// sourcery default = "Default value"
        let someKey: String
    }
}

Here, I need to use another comment for the default since one doesn't have the rawValue anymore. Furthermore, I need to specify the type of key, even if I'm not using it after. This is not as optimal as the enum case.

An alternative solution would be to give access to literal values of variables when they have some. For example, for the variable let foo = "bar" could maybe have a literalValue property returning "bar".

Ignore whitespace between template expressions

Is it possible for Sourcery (or maybe this is Stencil behavior) to ignore the whitespace between two template expressions?

For example, if I have the following template:

    	if let {{ variable.name }} = self.{{ variable.name }} {
    		{% if variable.annotations.key %}
    		{% if variable.annotations.serializable %}
    		toReturn["{{ variable.annotations.key }}"] = self.{{ variable.name }}.serialized()
    		{% else %}
    		toReturn["{{ variable.annotations.key }}"] = self.{{ variable.name }}
    		{% endif %}
    		{% else %}
    		{% if variable.annotations.serializable %}
    		toReturn["{{ variable.name }}"] = self.{{ variable.name }}.serialized()
    		{% else %}
    		toReturn["{{ variable.name }}"] = self.{{ variable.name }}
    		{% endif %}
    		{% endif %}
    	}

I get output similar to the following:

    	if let name = self.name {
    		
    		
    		toReturn["name"] = self.name
    		
    		
    	}

Ideally, this is what we'd get instead:

    	if let name = self.name {		
    		toReturn["name"] = self.name
    	}

Introduce code attributes that can be used with Sourcery

A very useful feature will be adding attributed comments into source code, this will allow us to generate more complex/flexible templates.

@conradev mentioned rust lib that has something like that https://github.com/serde-rs/serde

Other use-case examples might be adding json keys, skipping variable for equality generation etc.

API might look like this (Feel free to suggest better one):

// sourcery: skipEquality, jsonKey = "complex_name"
var variable: String

In templates you might access it like:

{% ifnot variable.attributes.skipEquality %} ///... 
{% endif %}

Homebrew support

It would be nice to have Sourcery in Homebrew.

Since it's part of the dev tooling, and not the build itself (arguably), I'd prefer not to use CocoaPods for this, but would still be nice to have easy updates with a dependency manager.

Associated values are missing some information

Right now for each enum associated values we have :

  • name <- name
  • typeName <- name of type of associated value

I suggest we could have similar information as variables :

  • name <- Name
  • type <- type of the variable, if known
  • typeName <- returns name of the type, including things like optional markup
  • unwrappedTypeName <- returns name of the type, unwrapping the optional e.g. for variable with type Int? this would return Int
  • isOptional <- whether is optional

The main motivation is to know if an associated value is optional

Inline generated code

After watching a talk mentioned in #14 I think it will be crucial to be able to provide an option to put generated code right into source files instead of separate files. The problem is that in separate files we can not access private variables, so either parser should just ignore them (as I mentioned in #38) or generated code will not compile anyway if it uses private variables. So we will not be able to use them for example for description and other stuff. Or we must enforce user to not use private variables which is not very nice too.

To achieve that we can simply always put generated code in the end of the file and separate it from other code with annotation comment (like "// sourcery: generated"). Then user will be just required to put his own code before that section, so that we don't override it. Feels like a reasonable tradeoff.

We might need to add a command line argument to define in what mode Sourcery should work. Then user can choose what templates should be used to generate code inline and what in separate files.
There should be also a separate collection of types accessible in templates like filetypes that will contain only types defined in that file for "inline" mode.

Bug in inheritedTypes.Protocol

I have the following code

protocol AutoEquatable {}

struct AStruct: Equatable {
  public static func == (l: AStruct, r: AStruct) -> Bool {
    return true
  }
}

struct Book: AutoEquatable {
  let aStruct: AStruct
}

If I generate the code for this template

{% for type in types.implementing.AutoEquatable %}
  {% for variable in type.storedVariables %}
    {% if variable.type.inheritedTypes.AutoEquatable %}
      {{ variable.type }}
    {% endif %}
  {% endfor %}
{% endfor %}

I should see an empty file, since Book doesn't contain any variable that has AutoEquatable as protocol.
But instead I see Astruct type definition (which has only Equatable as inheritedType).
If I remove the Equatable protocol from Astruct struct, the generated file is empty.

Basically it seems that variable.type.inheritedTypes.AutoEquatable always passes if variable.type.inheritedTypes is not empty. Note that I can use everything instead of AutoEquatable (like variable.type.inheritedTypes.TEST) and the result is the same

Correctly detecting enum rawValue type

Currently enums assume that the first type after : is a rawValue type. But that will fail in a couple of scenarios. There are several different ways to define valid enum:

  1. Enum explicitly implementing RawRepresentable:
enum MyEnum: RawRepresentable { //not necessary first or present directly
  typealias RawValue: Int //not necessary present
  var rawValue: Int { ... } //type can be RawValue
  init?(rawValue: Int) { ... } //type can be RawValue
}
  1. Implicit rawValue enum
enum StringEnum: String {
  case a, b, c
}

enum NumericEnum: Int { //or any other numeric type
  case one, two, three
}
  1. Enum with no rawValue at all
enum JustEnum {
 case a, b, c
}

I can suggest the following logic:

  1. if there are no inherited types - enum does not have raw value
  2. if there are inherited types:
    2.a if there is RawRepresentable at any position in inherited types:
    2.a.i check type of rawValue property, or
    2.a.ii check type of rawValue constructor argument, or
    2.a.iii fail otherwise (as SourceKitten does not provide info about typealiases) or scan type body content for typealias
    2.b if there is no RawReperesentable in inherited types get the first type as rawValue type if there is any

There are of course more scenarios that will still not work (like enum that implements protocol derived from RawRepresentable), but we can always improve in the future.

Ability to distinguish `var` from `let`

It would be great to have access to the type of the property, let or var.

I would like to contribute if possible, just need some opinions and directions! :)

👍

Support sections for annotations

Taking as an example AutoEquatable protocol it can be tedious to add /// sourcery: skipEquatable for each of the standard library types. Will it be possible to support sections of annotations the same ways as NS_ASSUME_NONNULL_BEGIN and NS_ASSUME_NONNULL_END, for example like this?

// sourcery: skipAnnotations begin

extension String: AutoEquatable {}
extension Int: AutoEquatable {}
...
// sourcery: skipAnnotations end

Add methods reflection

To be able to use Sourcery to generate code for such things like test doubles or dependency injection container it's vital to be able to access information about methods defined in class (or even on a protocol?).

To start method can be modeled like this:

class Method: NSObject {
  class Parameter: NSObject {
     var argumentLabel: String
     var parameterName: String
     var type: String
  }
  var parameters: [Parameter]
  var shortName: String //does not include external parameters names
  var fullName: String
  var returnType: String
  var accessLevel: AccessLevel
  var isStatic: Bool
  var isClass: Bool
}

Then in template it might be used something like this (taking Dobby as an example):

{% for type in types.classes %}
  class {{ type.name }}Mock: {{ type.name }} {
    {% for method in type.methods %}
    override func {{ method.shortName }}({% for parameter in method.parameters %}{% if parameter.argumentLabel %}{{ parameter. argumentLabel }} {% endif %}{{ parameter.parameterName}}: {{ parameter.type }}{% endfor %}) -> {{ method.returnType }} {
      /* record a method call */
    }
  }
{% endfor %}

Wrong RawType on enums

I came across this today, don't know if its a bug, bug it was not what I was expecting to be the RawType

{% for enum in types.enums %}{% if enum.rawType %}
RawType => {{ enum.rawType }}
{% endif %}{% endfor %}


enum Test1 {
	case one
}
// Does not generate code // ok!



enum Test2: String {
	case two
}
// Does generate code
RawType => String // ok!



enum Test3: Int {
	case three
}
// Does generate code
RawType => Int // ok!



enum Test4: Equatable {
	case four
}
// Does generate code
RawType => Equatable // it this suppouse to be the RawType? I was expecting to be nil

Thanks 👍

Recommend Projects

  • React photo React

    A declarative, efficient, and flexible JavaScript library for building user interfaces.

  • Vue.js photo Vue.js

    🖖 Vue.js is a progressive, incrementally-adoptable JavaScript framework for building UI on the web.

  • Typescript photo Typescript

    TypeScript is a superset of JavaScript that compiles to clean JavaScript output.

  • TensorFlow photo TensorFlow

    An Open Source Machine Learning Framework for Everyone

  • Django photo Django

    The Web framework for perfectionists with deadlines.

  • D3 photo D3

    Bring data to life with SVG, Canvas and HTML. 📊📈🎉

Recommend Topics

  • javascript

    JavaScript (JS) is a lightweight interpreted programming language with first-class functions.

  • web

    Some thing interesting about web. New door for the world.

  • server

    A server is a program made to process requests and deliver data to clients.

  • Machine learning

    Machine learning is a way of modeling and interpreting data that allows a piece of software to respond intelligently.

  • Game

    Some thing interesting about game, make everyone happy.

Recommend Org

  • Facebook photo Facebook

    We are working to build community through open source technology. NB: members must have two-factor auth.

  • Microsoft photo Microsoft

    Open source projects and samples from Microsoft.

  • Google photo Google

    Google ❤️ Open Source for everyone.

  • D3 photo D3

    Data-Driven Documents codes.