Skip to content
New issue

Have a question about this project? Sign up for a free GitHub account to open an issue and contact its maintainers and the community.

By clicking “Sign up for GitHub”, you agree to our terms of service and privacy statement. We’ll occasionally send you account related emails.

Already on GitHub? Sign in to your account

Rule Request: File Content Order #2294

Closed
Dschee opened this issue Jul 17, 2018 · 0 comments

Comments

Projects
None yet
2 participants
@Dschee
Copy link
Collaborator

commented Jul 17, 2018

This is a rule request. I'm willing to implement this myself and send a PR, but before doing that I'd like to know if the rule is wanted or not.

Rationale

When working in a team on a project, it can speed up both writing and especially finding code when all team members order things in a file the same way. While there may be cases where this is an anti-pattern to enable domain-specific grouping of things within a type or file, in many projects it can be useful to declare that a type should always represent one domain and be placed within its own file and things within a class shouldn't be grouped by domain, instead one should create additional types if more grouping is needed. This way types can be kept small and represent only one domain – and the order of things within a type can be checked more easily to ensure things are kept clean. This can also be a huge help in open-source projects with many contributors to ensure consistent style.

The requested rules should allow specifying an order for different types of things that can be part of a file and warnings should be shown if the order is broken somewhere. Here's a list of "type of things" a file could include:

  • Supporting Types (outside the main type body)
  • Cases (Enum only)
  • Type Aliases
  • Subtypes
  • Stored Type Properties
  • Computed Type Properties
  • Stored Instance Properties
  • Computed Instance Properties
  • IBOutlets
  • Initializers
  • Type Methods
  • Life-Cycle Methods
  • IBActions
  • Other Methods
  • Subscripts
  • Extensions (outside the main type body)

Example

For example, consider this view controller which includes most of the above listed things:

// Supporting Types
protocol TestViewControllerDelegate {
    func didPressTrackedButton()
}

class TestViewController: UIViewController {
    // Type Aliases
    typealias CompletionHandler = ((TestEnum) -> Void)

    // Subtypes
    class TestClass {
        // 10 lines
    }

    struct TestStruct {
        // 3 lines
    }

    enum TestEnum {
        // 5 lines
    }

    // Stored Type Properties
    static let cellIdentifier: String = "AmazingCell"

    // Stored Instance Properties
    var shouldLayoutView1: Bool!
    weak var delegate: TestViewControllerDelegate?
    private var hasLayoutedView1: Bool = false
    private var hasLayoutedView2: Bool = false

    // Computed Instance Properties
    private var hasAnyLayoutedView: Bool {
         return hasLayoutedView1 || hasLayoutedView2
    }

    // IBOutlets
    @IBOutlet private var view1: UIView!
    @IBOutlet private var view2: UIView!

    // Initializers
    override init(nibName nibNameOrNil: String?, bundle nibBundleOrNil: Bundle?) {
        super.init(nibName: nibNameOrNil, bundle: nibBundleOrNil)
    }

    required init?(coder aDecoder: NSCoder) {
        fatalError("init(coder:) has not been implemented")
    }

    // Type Methods
    static func makeViewController() -> TestViewController {
        // some code
    }

    // Life-Cycle Methods
    override func viewDidLoad() {
        super.viewDidLoad()

        view1.setNeedsLayout()
        view1.layoutIfNeeded()
        hasLayoutedView1 = true
    }

    override func viewDidLayoutSubviews() {
        super.viewDidLayoutSubviews()

        view2.setNeedsLayout()
        view2.layoutIfNeeded()
        hasLayoutedView2 = true
    }

    // IBActions
    @IBAction func goNextButtonPressed() {
        goToNextVc()
        delegate?.didPressTrackedButton()
    }

    @objc
    func goToRandomVcButtonPressed() {
        goToRandomVc()
    }

    // MARK: Other Methods
    func goToNextVc() { /* TODO */ }

    func goToInfoVc() { /* TODO */ }

    func goToRandomVc() {
        let viewCtrl = getRandomVc()
        present(viewCtrl, animated: true)
    }

    private func getRandomVc() -> UIViewController { return UIViewController() }

    // Subscripts
    subscript(_ someIndexThatIsNotEvenUsed: Int) -> String {
        get {
            return "This is just a test"
        }

        set {
            log.warning("Just a test", newValue)
        }
    }
}

// Extensions
extension TestViewController: UITableViewDataSource {
    func tableView(_ tableView: UITableView, numberOfRowsInSection section: Int) -> Int {
        return 1
    }

    func tableView(_ tableView: UITableView, cellForRowAt indexPath: IndexPath) -> UITableViewCell {
        return UITableViewCell()
    }
}

This type is sorted in a way which I suggest as the default order unless something else is configured.

Customizability

I think these rules must be opt-in and highly configurable. For example a basic configuration could look something like this:

file_types_order:
  custom:
    - supporting_types
    - main_type
    - extensions

type_contents_order:
  custom:
    - cases
    - type_aliases
    - subtypes
    - stored_type_properties
    - computed_type_properties
    - stored_instance_properties
    - computed_instance_properties
    - iboutlets
    - initializers
    - type_methods
    - life_cycle_methods
    - ibactions
    - other_methods
    - subscripts

Since some people might not want to differentiate between specific things, for example some might opt to mix up the order of stored and computed properties, one could also specify this:

file_types_order:
  custom:
    - supporting_types
    - main_type
    - extensions

type_contents_order:
  custom:
    - cases
    - type_aliases
    - subtypes
    - [stored_type_properties, computed_type_properties]
    - [stored_instance_properties, computed_instance_properties]
    - iboutlets
    - initializers
    - type_methods
    - life_cycle_methods
    - ibactions
    - other_methods
    - subscripts

This would allow stored and computed properties to be listed in mixed order, but still ensure properties are sorted below subtypes and above IBOutlets.


What do you think, would such a rule be added to SwiftLint? Are you missing any kinds of things within a subtype? Do you have other suggestions for the configuration? Any other ideas for the rules name?

I'm looking forward to hearing from you! 🙂

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment
You can’t perform that action at this time.