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

Add rule duplicate_id, which detect xml element duplicate id #126

Merged
merged 4 commits into from
Nov 20, 2019
Merged
Show file tree
Hide file tree
Changes from all commits
Commits
File filter

Filter by extension

Filter by extension

Conversations
Failed to load comments.
Loading
Jump to
Jump to file
Failed to load files.
Loading
Diff view
Diff view
8 changes: 8 additions & 0 deletions Rules.md
Original file line number Diff line number Diff line change
Expand Up @@ -95,3 +95,11 @@ Display error when views are ambiguous.
| Default Rule | `false` |

Check View as: set as a device specified by view_as_device_rule config.

## DuplicateIDRule

| identifier | `duplicate_id` |
|:---------------:|:---------------:|
| Default Rule | `true` |

Display warning when two element use the same id.
1 change: 1 addition & 0 deletions Sources/IBLinterKit/Rule.swift
Original file line number Diff line number Diff line change
Expand Up @@ -29,6 +29,7 @@ public struct Rules {
MisplacedViewRule.self,
ForceToEnableAutoLayoutRule.self,
DuplicateConstraintRule.self,
DuplicateIDRule.self,
StoryboardViewControllerId.self,
ImageResourcesRule.self,
CustomModuleRule.self,
Expand Down
57 changes: 57 additions & 0 deletions Sources/IBLinterKit/Rules/DuplicateIDRule.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,57 @@
//
// DuplicateIDRule.swift
// IBLinterKit
//
// Created by Eric Marchand on 2019/10/29.
//

import Foundation
import IBDecodable

extension Rules {

struct DuplicateIDRule: Rule {

static let identifier = "duplicate_id"
static let description = "Display warning when elements use same id."
static let isDefault = true

init(context: Context) {}

func validate(storyboard: StoryboardFile) -> [Violation] {
return validate(file: storyboard, in: storyboard.document)
}

func validate(xib: XibFile) -> [Violation] {
return validate(file: xib, in: xib.document)
}

private func validate<T: InterfaceBuilderFile>(file: T, in document: IBElement) -> [Violation] {
return duplicateIDs(in: document).map {
let message = "duplicate element id \($0.0)"
return Violation(
pathString: file.pathString,
message: message,
level: .warning)
}
}

private func duplicateIDs(in document: IBElement) -> [(String, [IBIdentifiable])] {
var byId: [String: [IBIdentifiable]] = [:]
var duplicateId: [String] = []
_ = document.browse { element in
if let identifiable = element as? IBIdentifiable {
if var arrayForId = byId[identifiable.id] {
duplicateId.append(identifiable.id)
arrayForId.append(identifiable)
byId[identifiable.id] = arrayForId
} else {
byId[identifiable.id] = [identifiable]
}
}
return true
}
return duplicateId.map { ($0, byId[$0] ?? []) }
}
}
}
Original file line number Diff line number Diff line change
@@ -0,0 +1,46 @@
<?xml version="1.0" encoding="UTF-8"?>
<document type="com.apple.InterfaceBuilder3.CocoaTouch.XIB" version="3.0" toolsVersion="14113" 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="14088"/>
<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>
<button opaque="NO" contentMode="scaleToFill" contentHorizontalAlignment="center" contentVerticalAlignment="center" buttonType="roundedRect" lineBreakMode="middleTruncation" translatesAutoresizingMaskIntoConstraints="NO" id="aEU-56-OK8">
<rect key="frame" x="164" y="457" width="46" height="30"/>
<constraints>
<constraint firstAttribute="height" relation="greaterThanOrEqual" constant="30" id="hhg-lH-Emq"/>
<constraint firstAttribute="height" priority="750" constant="30" id="s6a-gw-F2T"/>
</constraints>
<state key="normal" title="Button"/>
</button>
</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="vUN-kp-3ea" firstAttribute="bottom" secondItem="Pfc-VO-bsH" secondAttribute="bottom" constant="180" id="RXI-db-JkZ"/>
<constraint firstItem="ItZ-vi-bgh" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="iN0-l3-epB"/>
<constraint firstItem="ItZ-vi-bgh" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="aEU-56-OK8"/>
<constraint firstItem="Pfc-VO-bsH" firstAttribute="centerX" secondItem="iN0-l3-epB" secondAttribute="centerX" id="m0f-LQ-wgj"/>
<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>
24 changes: 24 additions & 0 deletions Tests/IBLinterKitTest/Rules/DuplicateIDRuleTests.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,24 @@
//
// DuplicateConstraintRuleTests.swift
// IBLinterKitTest
//
// Created by Eric Marchand on 2019/10/29.
//

@testable import IBLinterKit
import XCTest
import IBDecodable

class DuplicateIDRuleTests: XCTestCase {

let fixture = Fixture()

func testDuplicateId() {
let url = fixture.path("Resources/Rules/DuplicateIDRule/DuplicateID.xib")
let rule = Rules.DuplicateIDRule(context: .mock(from: .default))
let violations = try! rule.validate(xib: XibFile(url: url))
XCTAssertEqual(violations.count, 2)
let expectedMessages = ["iN0-l3-epB", "aEU-56-OK8"].map { "duplicate element id \($0)"}
XCTAssertEqual(violations.map { $0.message }, expectedMessages)
}
}