Skip to content

Commit

Permalink
Merge pull request #51 from fukagawa-satoru/rule_custom_module
Browse files Browse the repository at this point in the history
Add Rule custom module
  • Loading branch information
kateinoigakukun committed Jul 18, 2018
2 parents e084444 + 6dd34a3 commit d6f7a6a
Show file tree
Hide file tree
Showing 22 changed files with 820 additions and 21 deletions.
25 changes: 17 additions & 8 deletions Package.resolved
Original file line number Diff line number Diff line change
Expand Up @@ -33,8 +33,8 @@
"repositoryURL": "https://github.com/Quick/Nimble.git",
"state": {
"branch": null,
"revision": "21f4fed2052cea480f5f1d2044d45aa25fdfb988",
"version": "7.1.1"
"revision": "8023e3980d91b470ad073d6da843b73f2eeb1844",
"version": "7.1.2"
}
},
{
Expand All @@ -60,8 +60,8 @@
"repositoryURL": "https://github.com/antitypical/Result.git",
"state": {
"branch": null,
"revision": "7477584259bfce2560a19e06ad9f71db441fff11",
"version": "3.2.4"
"revision": "8fc088dcf72802801efeecba76ea8fb041fb773d",
"version": "4.0.0"
}
},
{
Expand All @@ -73,6 +73,15 @@
"version": "2.1.0"
}
},
{
"package": "SourceKitten",
"repositoryURL": "https://github.com/jpsim/SourceKitten.git",
"state": {
"branch": "master",
"revision": "8c293777af7ed710ecac6f1cae2335cdef2de317",
"version": null
}
},
{
"package": "Spectre",
"repositoryURL": "https://github.com/kylef/Spectre.git",
Expand All @@ -87,8 +96,8 @@
"repositoryURL": "https://github.com/drmohundro/SWXMLHash.git",
"state": {
"branch": null,
"revision": "17d992beb3aaeda403fd35f8d5e70ab1a8124f35",
"version": "4.6.0"
"revision": "2211b35c2e0e8b08493f86ba52b26e530cabb751",
"version": "4.7.0"
}
},
{
Expand All @@ -105,8 +114,8 @@
"repositoryURL": "https://github.com/jpsim/Yams.git",
"state": {
"branch": null,
"revision": "6652aa7b793d3c8a075db0614acb575fcaecf457",
"version": "0.7.0"
"revision": "618582e09699b577fa183bab7d88e3ee7d9a1d19",
"version": "1.0.0"
}
}
]
Expand Down
4 changes: 2 additions & 2 deletions Package.swift
Original file line number Diff line number Diff line change
Expand Up @@ -14,7 +14,7 @@ let package = Package(
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/IBDecodable/IBDecodable.git", .branch("master")),
.package(url: "https://github.com/Carthage/Commandant.git", .branch("master")),
.package(url: "https://github.com/jpsim/Yams.git", from: "0.4.1"),
.package(url: "https://github.com/jpsim/SourceKitten.git", from: "0.21.1"),
.package(url: "https://github.com/xcodeswift/xcproj.git", from: "4.3.0")
],
targets: [
Expand All @@ -25,7 +25,7 @@ let package = Package(
dependencies: ["IBLinterKit"]),
.target(
name: "IBLinterKit",
dependencies: ["IBDecodable", "Commandant", "Yams", "xcproj"]),
dependencies: ["IBDecodable", "Commandant", "SourceKittenFramework", "xcproj"]),
.testTarget(name: "IBLinterKitTest",
dependencies: ["IBLinterKit"])
]
Expand Down
30 changes: 24 additions & 6 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -73,7 +73,8 @@ Alternatively, if you've installed IBLinter via CocoaPods the script should look
| `enable_autolayout` | Force to use `useAutolayout` option |
| `duplicate_constraint` | Display warning when view has duplicated constraint. |
| `storyboard_viewcontroller_id` | Check that Storyboard ID same as ViewController class name. |
| `image_resources` | Check if image resouces are valid. |
| `image_resources` | Check if image resouces are valid. |
| `custom_module` | Check if custom class match custom module by `custom_module_rule` config. |


Pull requests are encouraged.
Expand All @@ -84,11 +85,22 @@ Pull requests are encouraged.
You can configure IBLinter by adding a `.iblinter.yml` file from project root directory.


| key | description |
|:-----------------|:-------------------------|
| `enabled_rules` | Enabled rules id. |
| `disabled_rules` | Disabled rules id. |
| `excluded` | Path to ignore for lint. |
| key | description |
|:---------------------|:--------------------------- |
| `enabled_rules` | Enabled rules id. |
| `disabled_rules` | Disabled rules id. |
| `excluded` | Path to ignore for lint. |
| `custom_module_rule` | Custom module rule configs. |

### CustomModuleConfig

You can configure `custom_module` rule by `CustomModuleConfig` list.

| key | description |
|:-----------|:---------------------------------------------------------------------------- |
| `module` | Module name. |
| `included` | Path to `*.swift` classes of the module for `custom_module` lint. |
| `excluded` | Path to ignore for `*.swift` classes of the module for `custom_module` lint. |


```yaml
Expand All @@ -98,4 +110,10 @@ disabled_rules:
- custom_class_name
excluded:
- Carthage
custom_module_rule:
- module: UIComponents
included:
- UIComponents/Classes
excluded:
- UIComponents/Classes/Config/Generated
```
7 changes: 6 additions & 1 deletion Sources/IBLinterKit/Config.swift
Original file line number Diff line number Diff line change
Expand Up @@ -12,12 +12,14 @@ public struct Config: Codable {
public let disabledRules: [String]
public let enabledRules: [String]
public let excluded: [String]
public let customModuleRule: [CustomModuleConfig]
public let reporter: String

enum CodingKeys: String, CodingKey {
case disabledRules = "disabled_rules"
case enabledRules = "enabled_rules"
case excluded = "excluded"
case customModuleRule = "custom_module_rule"
case reporter = "reporter"
}

Expand All @@ -28,13 +30,15 @@ public struct Config: Codable {
disabledRules = []
enabledRules = []
excluded = []
customModuleRule = []
reporter = "xcode"
}

init(disabledRules: [String], enabledRules: [String], excluded: [String], reporter: String) {
init(disabledRules: [String], enabledRules: [String], excluded: [String], customModuleRule: [CustomModuleConfig], reporter: String) {
self.disabledRules = disabledRules
self.enabledRules = enabledRules
self.excluded = excluded
self.customModuleRule = customModuleRule
self.reporter = reporter
}

Expand All @@ -43,6 +47,7 @@ public struct Config: Codable {
disabledRules = try container.decodeIfPresent(Optional<[String]>.self, forKey: .disabledRules).flatMap { $0 } ?? []
enabledRules = try container.decodeIfPresent(Optional<[String]>.self, forKey: .enabledRules).flatMap { $0 } ?? []
excluded = try container.decodeIfPresent(Optional<[String]>.self, forKey: .excluded).flatMap { $0 } ?? []
customModuleRule = try container.decodeIfPresent(Optional<[CustomModuleConfig]>.self, forKey: .customModuleRule).flatMap { $0 } ?? []
reporter = try container.decodeIfPresent(Optional<String>.self, forKey: .reporter).flatMap { $0 } ?? "xcode"
}

Expand Down
43 changes: 43 additions & 0 deletions Sources/IBLinterKit/CustomModuleConfig.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,43 @@
//
// CustomModuleConfig.swift
// IBLinterKit
//
// Created by FukagawaSatoru on 2018/7/3.
//

import Foundation
import Yams

public struct CustomModuleConfig: Codable {
public let module: String
public let included: [String]
public let excluded: [String]

enum CodingKeys: String, CodingKey {
case module = "module"
case included = "included"
case excluded = "excluded"
}

public static let `default` = CustomModuleConfig.init()

private init() {
module = ""
included = []
excluded = []
}

init(module: String, included: [String], excluded: [String]) {
self.module = module
self.included = included
self.excluded = excluded
}

public init(from decoder: Decoder) throws {
let container = try decoder.container(keyedBy: CodingKeys.self)
module = try container.decodeIfPresent(Optional<String>.self, forKey: .module).flatMap { $0 } ?? ""
included = try container.decodeIfPresent(Optional<[String]>.self, forKey: .included).flatMap { $0 } ?? []
excluded = try container.decodeIfPresent(Optional<[String]>.self, forKey: .excluded).flatMap { $0 } ?? []
}

}
92 changes: 92 additions & 0 deletions Sources/IBLinterKit/Rules/CustomModuleRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,92 @@
//
// CustomModuleRule.swift
// IBLinterKit
//
// Created by FukagawaSatoru on 2018/7/3.
//

import Foundation
import IBDecodable
import SourceKittenFramework
import xcproj

private extension XibFile {
var fileExtension: String {
return URL.init(fileURLWithPath: pathString).pathExtension
}
var fileNameWithoutExtension: String {
return fileName.replacingOccurrences(of: ".\(fileExtension)", with: "")
}
}

private extension StoryboardFile {
var fileExtension: String {
return URL.init(fileURLWithPath: pathString).pathExtension
}
var fileNameWithoutExtension: String {
return fileName.replacingOccurrences(of: ".\(fileExtension)", with: "")
}
}

extension Rules {

public struct CustomModuleRule: Rule {

public static let identifier: String = "custom_module"

private var moduleClasses: [String:[String]] = [:]

public init(context: Context) {
for customModuleConfig in context.config.customModuleRule {
let paths = customModuleConfig.included.flatMap { glob(pattern: "\($0)/**/*.swift") }
let excluded = customModuleConfig.excluded.flatMap { glob(pattern: "\($0)/**/*.swift") }
let lintablePaths = paths.filter { !excluded.map { $0.absoluteString }.contains($0.absoluteString) }
var classes: [String] = []
for path in lintablePaths {
let file = SourceKittenFramework.File(path: path.relativePath)
let fileClasses: [String] = file?.structure.dictionary.substructure.compactMap { dictionary in
if let kind = dictionary.kind, SwiftDeclarationKind(rawValue: kind) == .class {
return dictionary.name
}
return nil
} ?? []
classes += fileClasses
}
self.moduleClasses[customModuleConfig.module] = classes
}
}

public func validate(xib: XibFile) -> [Violation] {
guard let views = xib.document.views else { return [] }
return views.flatMap { validate(for: $0.view, file: xib, fileNameWithoutExtension: xib.fileNameWithoutExtension) }
}

public func validate(storyboard: StoryboardFile) -> [Violation] {
guard let scenes = storyboard.document.scenes else { return [] }
let views = scenes.compactMap { $0.viewController?.viewController.rootView }
return views.flatMap { validate(for: $0, file: storyboard, fileNameWithoutExtension: storyboard.fileNameWithoutExtension) }
}

private func validate<T: InterfaceBuilderFile>(for view: ViewProtocol, file: T, fileNameWithoutExtension: String) -> [Violation] {
let violation: [Violation] = {
guard let customClass = view.customClass else { return [] }
for moduleName in moduleClasses.keys {
if let classes = moduleClasses[moduleName] {
if classes.contains(customClass) {
if let customModule = view.customModule {
if moduleName == customModule {
return []
}
}
let message = "It does not match custom module rule in \(fileNameWithoutExtension). Custom module of \(customClass) is \(moduleName)"
return [Violation(pathString: file.pathString, message: message, level: .error)]
}
}
}
return []
}()
return violation + (view.subviews?.flatMap { validate(for: $0.view, file: file, fileNameWithoutExtension: fileNameWithoutExtension) } ?? [])
}

}
}
3 changes: 2 additions & 1 deletion Sources/IBLinterKit/Rules/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -24,7 +24,8 @@ public struct Rules {
ForceToEnableAutoLayoutRule.self,
DuplicateConstraintRule.self,
StoryboardViewControllerId.self,
ImageResourcesRule.self
ImageResourcesRule.self,
CustomModuleRule.self
]
}

Expand Down
Loading

0 comments on commit d6f7a6a

Please sign in to comment.