Skip to content

Commit

Permalink
Merge branch 'master' of github.com:kateinoigakukun/IBLinter
Browse files Browse the repository at this point in the history
  • Loading branch information
kateinoigakukun committed Dec 23, 2017
2 parents c41cfde + 4f42248 commit c2272c9
Show file tree
Hide file tree
Showing 6 changed files with 119 additions and 7 deletions.
6 changes: 3 additions & 3 deletions README.md
Original file line number Diff line number Diff line change
@@ -1,20 +1,20 @@
# IBLinter

A linter tool to normarize `.xib` and `.storyboard` files. Inspired by [realm/SwiftLint](https://github.com/realm/SwiftLint)
A linter tool to normalize `.xib` and `.storyboard` files. Inspired by [realm/SwiftLint](https://github.com/realm/SwiftLint)

![](assets/warning.png)

## Installation

```
```sh
$ brew install kateinoigakukun/homebrew-tap/iblinter
```

## Usage

You can see all description by `iblinter help`

- `lint` Print lint warnings and errors (default command)
- `lint` prints lint warnings and errors (default command)
- `--path` default is current directory.


Expand Down
5 changes: 3 additions & 2 deletions Sources/IBLinterCore/InterfaceBuilderView.swift
Original file line number Diff line number Diff line change
Expand Up @@ -1055,8 +1055,9 @@ extension InterfaceBuilderNode {
switch (lhs, rhs) {
case (.left, .left), (.right, .right), (.top, .top), (.bottom, .bottom),
(.leading, .leading), (.trailing, .trailing), (.width, .width),
(.height, height), (.centerX, .centerX), (.leftMargin, .leftMargin),
(.rightMargin, .rightMargin), (.topMargin, .topMargin), (.bottomMargin, .bottomMargin),
(.height, height), (.centerX, .centerX), (.centerY, .centerY),
(.leftMargin, .leftMargin), (.rightMargin, .rightMargin),
(.topMargin, .topMargin), (.bottomMargin, .bottomMargin),
(.leadingMargin, .leadingMargin), (.trailingMargin, .trailingMargin): return true
case (.other(let msg1), .other(let msg2)): return msg1 == msg2
default: return false
Expand Down
66 changes: 66 additions & 0 deletions Sources/IBLinterKit/Rules/DuplicateConstraintRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,66 @@
//
// DuplicateConstraintRule.swift
// IBLinterKit
//
// Created by SaitoYuta on 2017/12/23.
//

import Foundation
import IBLinterCore

extension Rules {

public struct DuplicateConstraintRule: Rule {

public static let identifier: String = "duplicate_constraint"

public init() {}

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

public func validate(xib: XibFile) -> [Violation] {
return xib.document.views?.flatMap { validate(for: $0, file: xib)} ?? []
}

private func validate(for view: ViewProtocol, file: InterfaceBuilderFile) -> [Violation] {
return duplicateConstraints(for: view.constraints ?? []).map {
let message = "duplicate constraint \($0.id) (firstItem: \($0.firstItem ?? "nil") attribute: \($0.firstAttribute.map(String.init(describing: )) ?? "nil") secondItem: \($0.secondItem ?? "nil") attribute: \($0.secondAttribute.map(String.init(describing: )) ?? "nil"))"
return Violation(
interfaceBuilderFile: file,
message: message,
level: .warning)
}
}

private typealias Constraint = InterfaceBuilderNode.View.Constraint

private func duplicateConstraints(for constraints: [Constraint]) -> [Constraint] {

var duplicateConstraints: [Constraint] = []
var uniqueConstraints: [Constraint] = []

constraints.forEach { constraint in
if uniqueConstraints.contains(where: { equal(lhs: $0, rhs: constraint) }) {
duplicateConstraints.append(constraint)
} else {
uniqueConstraints.append(constraint)
}
}
return duplicateConstraints
}

private func equal(lhs: Constraint, rhs: Constraint) -> Bool {
let sameItems = (lhs.firstItem == rhs.firstItem && lhs.secondItem == rhs.secondItem)
let reverseItems = (lhs.firstItem == rhs.secondItem && lhs.secondItem == rhs.firstItem)
let sameAttributes = (lhs.firstAttribute == rhs.firstAttribute && lhs.secondAttribute == rhs.secondAttribute)
let reverseAttributes = (lhs.secondAttribute == rhs.firstAttribute && lhs.firstAttribute == rhs.secondAttribute)
let sameConstant = lhs.constant == rhs.constant
let reverseConstaint = lhs.constant == rhs.constant.map(-)

return (sameItems && sameAttributes && sameConstant) || (reverseItems && reverseAttributes && reverseConstaint)
}
}
}
6 changes: 4 additions & 2 deletions Sources/IBLinterKit/Rules/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -21,14 +21,16 @@ public struct Rules {
CustomClassNameRule.self,
RelativeToMarginRule.self,
MisplacedViewRule.self,
ForceToEnableAutoLayoutRule.self
ForceToEnableAutoLayoutRule.self,
DuplicateConstraintRule.self
]
}

static var defaultRules: [Rule.Type] {
return [
CustomClassNameRule.self,
ForceToEnableAutoLayoutRule.self
ForceToEnableAutoLayoutRule.self,
DuplicateConstraintRule.self
]
}

Expand Down
36 changes: 36 additions & 0 deletions Tests/IBLinterKitTest/Resources/DuplicateConstraint.xib
Original file line number Diff line number Diff line change
@@ -0,0 +1,36 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="13196" targetRuntime="iOS.CocoaTouch" propertyAccessControl="none" useAutolayout="YES" useTraitCollections="YES" useSafeAreas="YES" colorMatched="YES">
<device id="retina4_7" orientation="portrait">
<adaptation id="fullscreen"/>
</device>
<dependencies>
<plugIn identifier="com.apple.InterfaceBuilder.IBCocoaTouchPlugin" version="13173"/>
<capability name="Safe area layout guides" minToolsVersion="9.0"/>
<capability name="documents saved in the Xcode 8 format" minToolsVersion="8.0"/>
</dependencies>
<objects>
<placeholder placeholderIdentifier="IBFilesOwner" id="-1" userLabel="File's Owner"/>
<placeholder placeholderIdentifier="IBFirstResponder" id="-2" customClass="UIResponder"/>
<view contentMode="scaleToFill" id="iN0-l3-epB">
<rect key="frame" x="0.0" y="0.0" width="375" height="667"/>
<autoresizingMask key="autoresizingMask" widthSizable="YES" heightSizable="YES"/>
<subviews>
<label opaque="NO" userInteractionEnabled="NO" contentMode="left" horizontalHuggingPriority="251" verticalHuggingPriority="251" text="Label" textAlignment="natural" lineBreakMode="tailTruncation" baselineAdjustment="alignBaselines" adjustsFontSizeToFit="NO" translatesAutoresizingMaskIntoConstraints="NO" id="ItZ-vi-bgh">
<rect key="frame" x="166" y="323" width="42" height="21"/>
<fontDescription key="fontDescription" type="system" pointSize="17"/>
<nil key="textColor"/>
<nil key="highlightedColor"/>
</label>
</subviews>
<color key="backgroundColor" red="1" green="1" blue="1" alpha="1" colorSpace="custom" customColorSpace="sRGB"/>
<constraints>
<constraint firstItem="vUN-kp-3ea" firstAttribute="top" secondItem="ItZ-vi-bgh" secondAttribute="top" constant="-303" id="8Jq-td-PfE"/>
<constraint firstItem="ItZ-vi-bgh" firstAttribute="top" secondItem="vUN-kp-3ea" secondAttribute="top" constant="303" id="Prh-uf-M6D"/>
<constraint firstItem="ItZ-vi-bgh" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="VAb-jS-xKl"/>
<constraint firstItem="ItZ-vi-bgh" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="aEU-56-OK8"/>
<constraint firstItem="ItZ-vi-bgh" firstAttribute="centerY" secondItem="iN0-l3-epB" secondAttribute="centerY" id="zWU-iS-F0Z"/>
</constraints>
<viewLayoutGuide key="safeArea" id="vUN-kp-3ea"/>
</view>
</objects>
</document>
7 changes: 7 additions & 0 deletions Tests/IBLinterKitTest/RuleTest.swift
Original file line number Diff line number Diff line change
Expand Up @@ -17,5 +17,12 @@ class RuleTest: XCTestCase {
let violations = rule.validate(storyboard: StoryboardFile.init(path: path))
XCTAssertEqual(violations.count, 1)
}

func testDuplicateConstraint() {
let path = "Tests/IBLinterKitTest/Resources/DuplicateConstraint.xib"
let rule = Rules.DuplicateConstraintRule.init()
let violations = rule.validate(xib: XibFile.init(path: path))
XCTAssertEqual(violations.count, 2)
}
}

0 comments on commit c2272c9

Please sign in to comment.