# Contributing to SwiftInspector

:+1: Thanks for taking the time to contribute to Swift Inspector! :+1:

This document explains some of the guidelines around contributing to this project as well as how this project is structured, so you can jump in!

## Alignment first

We ask you that if you have an idea for a new feature you [open an issue](../../issues/new).

## Submitting your changes

Whenever your feature is ready for review, please [open a PR](../../pull/new/master) with a clear list of what you've done.

For any change you make, we ask you to also **add corresponding unit tests**.

## How to contribute

### Structure of SwiftInspector

SwiftInspector is divided into four main parts:

#### SwiftInspector

Contains the main executable for this command line tool. It only contains the entry point `main.swift` file.

#### SwiftInspectorCommand

Contains all the files for managing commands and options for these commands. We can think of this as the *frontend* of this project.

#### SwiftInspectorAnalyzers

Comprises this project's analyzers. Any file related to analyzing Swift code should be put here. This is the layer that the Command interacts with directly.

#### SwiftInspectorVisitors

Comprises this project's syntax visitors. Any file that visits Swift syntax nodes should be put here. Analyzers should use the visitors in this module.

## Suggested workflow

### Writing a new Command

To add a new command create a `YourCommand.swift` file inside `SwiftInspectorCommand`  and add it to the `InspectorCommand` subcommands. Your command should delegate to `SwiftInspectorAnalyzer` for all the logic related to analyzing Swift code.

When you're ready to write a new command, I suggest you start by writing unit tests by relying on the [TestTask.swift](https://github.com/fdiaz/SwiftInspector/blob/407f34bb93df750d95cedaa10f656f0586d0769e/Sources/SwiftInspectorCommands/Tests/TestTask.swift) file to create fake commands with arguments:

```swift
private struct YourNewCommand {
  fileprivate static func run(path: String, arguments: [String] = []) throws -> TaskStatus {
    let arguments = ["newcommand", "--path", path] + arguments
    return try TestTask.run(withArguments: arguments)
  }
}
```

Refer to the [tests in the Commands target](https://github.com/fdiaz/SwiftInspector/tree/407f34bb93df750d95cedaa10f656f0586d0769e/Sources/SwiftInspectorCommands/Tests) for examples.

### Writing new Analyzer functionality

Since we want to separate the commands from the analyzer functionality, you should abstract your analyzer functionality in a class that lives in `SwiftInspectorAnalyzer`. Analyzers are a thin bridge between commands and syntax visitors – they are responsible for kicking off syntax visitation and then packaging up and returning the information gathered from syntax visitors.

### Writing new Visitor functionality

Code that visits Swift syntax nodes should be live in the `SwiftInspectorVisitors` module.

I suggest relying on the [Swift AST Explorer](https://swift-ast-explorer.com/) to understand the AST better and play around with different use cases.

When you're ready to write some code, I suggest you to start by writing unit tests by relying on the [Temporary.swift](https://github.com/fdiaz/SwiftInspector/blob/be2efb40fb1d085e69ae92a873c64fab9b66fa9a/Sources/SwiftInspectorTestHelpers/Temporary.swift) file to create fake files for testing.

```swift
context("when something happens") {
  beforeEach {
    fileURL = try! Temporary.makeFile(
      content: """
               typealias SomeTypealias = TypeA & TypeB
               """
    )
  }

  it("something happens") {
    let result = try? sut.analyze(fileURL: fileURL)
    expect(result) == Something
  }
}
```

Refer to the [tests in the Analyzer target](https://github.com/fdiaz/SwiftInspector/tree/407f34bb93df750d95cedaa10f656f0586d0769e/Sources/SwiftInspectorAnalyzers/Tests) for examples.

### Things to consider:
- We use Quick and Nimble in this repo, we rely on the following convention:
  - Use `describe` blocks for each internal and public method
  - Use `context` to setup different scenarios (e.g. "when A happens")
  - Only use one assert per test whenever possible