Skip to content

Commit

Permalink
Implement sort swift import
Browse files Browse the repository at this point in the history
  • Loading branch information
vikage committed Aug 20, 2021
1 parent 0b3431b commit 57822ac
Show file tree
Hide file tree
Showing 7 changed files with 420 additions and 8 deletions.
2 changes: 2 additions & 0 deletions .gitignore
Original file line number Diff line number Diff line change
@@ -0,0 +1,2 @@
*.DS_Store
SourceLint.xcodeproj/xcuserdata
47 changes: 47 additions & 0 deletions SourceEditorExt/Info.plist
Original file line number Diff line number Diff line change
@@ -0,0 +1,47 @@
<?xml version="1.0" encoding="UTF-8"?>
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>CFBundleDevelopmentRegion</key>
<string>$(DEVELOPMENT_LANGUAGE)</string>
<key>CFBundleDisplayName</key>
<string>Swift source lint</string>
<key>CFBundleExecutable</key>
<string>$(EXECUTABLE_NAME)</string>
<key>CFBundleIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER)</string>
<key>CFBundleInfoDictionaryVersion</key>
<string>6.0</string>
<key>CFBundleName</key>
<string>Swift source lint</string>
<key>CFBundlePackageType</key>
<string>$(PRODUCT_BUNDLE_PACKAGE_TYPE)</string>
<key>CFBundleShortVersionString</key>
<string>1.0</string>
<key>CFBundleVersion</key>
<string>1</string>
<key>LSMinimumSystemVersion</key>
<string>$(MACOSX_DEPLOYMENT_TARGET)</string>
<key>NSExtension</key>
<dict>
<key>NSExtensionAttributes</key>
<dict>
<key>XCSourceEditorCommandDefinitions</key>
<array>
<dict>
<key>XCSourceEditorCommandClassName</key>
<string>$(PRODUCT_MODULE_NAME).SortImportCommand</string>
<key>XCSourceEditorCommandIdentifier</key>
<string>$(PRODUCT_BUNDLE_IDENTIFIER).SortImportCommand</string>
<key>XCSourceEditorCommandName</key>
<string>Sort imports</string>
</dict>
</array>
<key>XCSourceEditorExtensionPrincipalClass</key>
<string>$(PRODUCT_MODULE_NAME).SourceEditorExtension</string>
</dict>
<key>NSExtensionPointIdentifier</key>
<string>com.apple.dt.Xcode.extension.source-editor</string>
</dict>
</dict>
</plist>
143 changes: 143 additions & 0 deletions SourceEditorExt/SortImportCommand.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,143 @@
//
// SortImportCommand.swift
// SourceEditorExt
//
// Created by Thanh Vu on 20/08/2021.
//

import Foundation
import XcodeKit

struct SortImportProcessResult {
var systemFrameWorkImports: [String]
var ribsInterfaceImports: [String]
var normalFrameWorkImports: [String]
}

class SortImportCommand: NSObject, XCSourceEditorCommand {

func perform(with invocation: XCSourceEditorCommandInvocation, completionHandler: @escaping (Error?) -> Void ) -> Void {
// Implement your command here, invoking the completion handler when done. Pass it nil on success, and an NSError on failure.
let sourceLines = invocation.buffer.lines
sort(sourceLines: sourceLines)
completionHandler(nil)
}

func getImportRange(lines: NSMutableArray) -> NSRange {
var firstImportIndex: Int?
var lastImportIndex: Int?
for index in 0..<lines.count {
let line = lines[index]

if let lineStr = line as? String {
if lineStr.trimSourceLine().hasPrefix("import ") {
if firstImportIndex == nil {
firstImportIndex = index
}

lastImportIndex = index
continue
}

if lineStr.trimSourceLine() == "" {
continue
}

if firstImportIndex != nil {
break
}
}
}

if firstImportIndex != nil {
return NSRange.init(location: firstImportIndex!, length: lastImportIndex! - firstImportIndex! + 1)
}

return NSRange.init(location: 0, length: 0)
}

private func processImportLines(lines: [String]) -> SortImportProcessResult {
let systemFrameworks = ["UIKit", "Foundation", "CoreGraphic", "MetalKit", "AVKit", "ARKit"]
var systemFrameWorkImports = [String]()
var ribsInterfaceImports = [String]()
var normalFrameWorkImports = [String]()

lines.forEach { importLine in
var isImportFramework = false
systemFrameworks.forEach { systemFrameworkName in
if importLine.contains(systemFrameworkName) {
isImportFramework = true
}
}

if isImportFramework {
systemFrameWorkImports.append(importLine)
} else if importLine.trimSourceLine().hasSuffix("Interfaces") {
ribsInterfaceImports.append(importLine)
} else {
normalFrameWorkImports.append(importLine)
}
}

systemFrameWorkImports.sort()
ribsInterfaceImports.sort()
normalFrameWorkImports.sort()

return SortImportProcessResult(systemFrameWorkImports: systemFrameWorkImports, ribsInterfaceImports: ribsInterfaceImports, normalFrameWorkImports: normalFrameWorkImports)
}

private func getImportLineFrom(range: NSRange, sourceLines: NSMutableArray) -> [String] {
let importLines = sourceLines.subarray(with: range)
var importLinesString = [String]()

importLines.forEach { line in
if let lineString = line as? String, lineString.trimSourceLine() != "" {
importLinesString.append(lineString.trimSourceLine())
}
}

return importLinesString
}

func insertNewImportLines(processResult: SortImportProcessResult, sourceLines: NSMutableArray, firstImportIndex: Int) {
var insertIndex: Int = firstImportIndex
processResult.systemFrameWorkImports.forEach { importLine in
sourceLines.insert(importLine, at: insertIndex)
insertIndex += 1
}

if !processResult.systemFrameWorkImports.isEmpty && (!processResult.normalFrameWorkImports.isEmpty || !processResult.ribsInterfaceImports.isEmpty) {
sourceLines.insert("", at: insertIndex)
insertIndex += 1
}

processResult.normalFrameWorkImports.forEach { importLine in
sourceLines.insert(importLine.trimSourceLine(), at: insertIndex)
insertIndex += 1
}

if !processResult.ribsInterfaceImports.isEmpty {
sourceLines.insert("", at: insertIndex)
insertIndex += 1
}

processResult.ribsInterfaceImports.forEach { importLine in
sourceLines.insert(importLine, at: insertIndex)
insertIndex += 1
}
}

func sort(sourceLines: NSMutableArray) {
let importRange = self.getImportRange(lines: sourceLines)

if importRange.length == 0 {
return
}

let importLines = self.getImportLineFrom(range: importRange, sourceLines: sourceLines)
sourceLines.removeObjects(in: importRange)

let processResult = processImportLines(lines: importLines)
insertNewImportLines(processResult: processResult, sourceLines: sourceLines, firstImportIndex: importRange.location)
}
}
Original file line number Diff line number Diff line change
Expand Up @@ -2,13 +2,7 @@
<!DOCTYPE plist PUBLIC "-//Apple//DTD PLIST 1.0//EN" "http://www.apple.com/DTDs/PropertyList-1.0.dtd">
<plist version="1.0">
<dict>
<key>SchemeUserState</key>
<dict>
<key>SourceLint.xcscheme_^#shared#^_</key>
<dict>
<key>orderHint</key>
<integer>0</integer>
</dict>
</dict>
<key>com.apple.security.app-sandbox</key>
<true/>
</dict>
</plist>
25 changes: 25 additions & 0 deletions SourceEditorExt/SourceEditorExtension.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,25 @@
//
// SourceEditorExtension.swift
// SourceEditorExt
//
// Created by Thanh Vu on 20/08/2021.
//

import Foundation
import XcodeKit

class SourceEditorExtension: NSObject, XCSourceEditorExtension {

func extensionDidFinishLaunching() {
NSLog("Launch")
}


/*
var commandDefinitions: [[XCSourceEditorCommandDefinitionKey: Any]] {
// If your extension needs to return a collection of command definitions that differs from those in its Info.plist, implement this optional property getter.
return []
}
*/

}
18 changes: 18 additions & 0 deletions SourceEditorExt/StringExtensions.swift
Original file line number Diff line number Diff line change
@@ -0,0 +1,18 @@
//
// StringExtensions.swift
// SourceEditorExt
//
// Created by Thanh Vu on 20/08/2021.
//

import Foundation

extension String {
func trimSourceLine() -> String {
let result = NSMutableString.init(string: self)
let regexReplaceMultiSpace = try! NSRegularExpression(pattern: "[\\ ]+", options: [])
regexReplaceMultiSpace.replaceMatches(in: result, options: .reportCompletion, range: NSRange.init(location: 0, length: result.length), withTemplate: " ")

return result.trimmingCharacters(in: .whitespacesAndNewlines)
}
}
Loading

0 comments on commit 57822ac

Please sign in to comment.