Skip to content

Commit

Permalink
Expand list of reserved objc keywords to cover most conflicting case (#…
Browse files Browse the repository at this point in the history
…79)

* Expand list of reserved objc keywords to cover most conflicting cases. This is
an incomplete list and will likely have more additions in the future.

* Update repository to Swift 4

* Add initial docker compose configuration and pipeline settings

* Integrate linux buildkite steps in plank
  • Loading branch information
rahul-malik authored Sep 24, 2017
1 parent 3fc1bea commit c5ed50a
Show file tree
Hide file tree
Showing 29 changed files with 250 additions and 54 deletions.
30 changes: 30 additions & 0 deletions .buildkite/plank-pipeline.yml
Original file line number Diff line number Diff line change
@@ -0,0 +1,30 @@
steps:
- name: ":docker: Build base image with sources"
plugins:
docker-compose#v1.5.0:
build: app
image-repository: buildkite-registry.pinadmin.com/registry
volumes:
- ".:/app/mnt/buildkite-builds/volumes"
config: docker-compose.yml
agents:
queue: pinboard-ios
- wait: ~
- name: "Build :iphone: :airplane:"
command: "swift build"
timeout_in_minutes: 30
agents:
queue: pinboard-ios
plugins:
docker-compose#v1.5.0:
run: app
config: docker-compose.yml
- name: "Test :iphone: :airplane:"
command: "swift test"
timeout_in_minutes: 30
agents:
queue: pinboard-ios
plugins:
docker-compose#v1.5.0:
run: app
config: docker-compose.yml
7 changes: 7 additions & 0 deletions .buildkite/upload_pipeline.sh
Original file line number Diff line number Diff line change
@@ -0,0 +1,7 @@
#!/bin/bash

# Only run pipeline uploads in buildkite environments
if [[ ! -z ${BUILDKITE} ]]; then
buildkite-agent pipeline upload .buildkite/plank-pipeline.yml
fi

2 changes: 1 addition & 1 deletion .swift-version
Original file line number Diff line number Diff line change
@@ -1 +1 @@
3.1
4.0
4 changes: 2 additions & 2 deletions BUILD
Original file line number Diff line number Diff line change
Expand Up @@ -5,14 +5,14 @@ swift_library(
name = "PlankCore",
srcs = glob(["Sources/Core/*.swift"]),
module_name = "Core",
swift_version = 3,
swift_version = 4,
copts = ["-whole-module-optimization"]
)

swift_library(
name = "PlankLib",
srcs = glob(["Sources/plank/*.swift"]),
swift_version = 3,
swift_version = 4,
copts = ["-whole-module-optimization"],
deps = [":PlankCore"]
)
Expand Down
17 changes: 6 additions & 11 deletions Dockerfile
Original file line number Diff line number Diff line change
@@ -1,21 +1,16 @@
# Base image from Swiftenv with Swift version 3.0.2

FROM kylef/swiftenv:latest
# Base image from SwiftDocker
# https://hub.docker.com/r/swiftdocker/swift/
FROM swiftdocker/swift
MAINTAINER Pinterest
RUN swiftenv install 3.1

# Vim config so we have an editor available
RUN apt-get update && \
apt-get install -y --no-install-recommends \
vim clang libicu-dev libcurl4-openssl-dev libssl-dev

# Install plank
COPY . /usr/local/plank
RUN cd /usr/local/plank && swift build -c release

ENV plank_HOME /usr/local/plank
ENV PATH ${plank_HOME}/.build/release:${PATH}

# Uncomment to make `plank` the default action of `docker run [image_name]`
#ENTRYPOINT ["plank"]
#CMD ["help"]
# Copy plank sources
WORKDIR /plank

2 changes: 1 addition & 1 deletion Examples/Cocoa/Package.swift
Original file line number Diff line number Diff line change
@@ -1,6 +1,6 @@
import PackageDescription

let package = Package(
name: "Objc"
name: "Objective_C"
)

File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
File renamed without changes.
Original file line number Diff line number Diff line change
@@ -1,14 +1,15 @@
import Foundation
import XCTest

@testable import objc
@testable import Objective_C

// Helper for comparing model dictionaries
public func ==(lhs: [AnyHashable: Any], rhs: [AnyHashable: Any] ) -> Bool {
return NSDictionary(dictionary: lhs).isEqual(to: rhs)
}

class ObjcTests: XCTestCase {
class ObjcTestSuite: XCTestCase {

func testBasicObjectInitialization() {
let imageModelDictionary: [AnyHashable: Any] = [
"height": 12,
Expand Down
5 changes: 4 additions & 1 deletion Makefile
Original file line number Diff line number Diff line change
Expand Up @@ -4,7 +4,7 @@

.PHONY: all clean build test archive

all: clean build test integration_test archive
all: upload_pipeline clean build test integration_test archive

clean:
xcrun swift package clean
Expand All @@ -24,6 +24,9 @@ integration_test: build
archive:
xcrun swift build -c release -Xswiftc -static-stdlib

upload_pipeline:
.buildkite/upload_pipeline.sh

build_test_index_linux:
swift Utility/GenerateTestCaseProvider.swift $(PWD)/Tests/CoreTests

Expand Down
5 changes: 4 additions & 1 deletion Sources/Core/JSIR.swift
Original file line number Diff line number Diff line change
Expand Up @@ -136,7 +136,10 @@ public struct JSIR {
return [
JSRuntimeFile.runtimeImports(),
// TODO: JS: We should find a better way to remove a cyclic import as filtering it here
classNames.filter { $0 != "\(myName)Type" }.sorted().map { JSIR.fileImportStmt($0, $0) }.joined(separator: "\n")
(classNames.filter { $0 != "\(myName)Type" } as [String])
.sorted()
.map { JSIR.fileImportStmt($0, $0) }
.joined(separator: "\n")
]
case .typeDecl(name: let className, extends: _, properties: let properties):
let nullability = { (prop: SchemaObjectProperty) -> String in
Expand Down
4 changes: 3 additions & 1 deletion Sources/Core/ObjectiveCEqualityExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -84,7 +84,9 @@ extension ObjCFileRenderer {
}

// Performance optimization - compare primitives before resorting to more expensive `isEqual` calls
let sortedProps = self.properties.sorted { $0.0.1.schema.isObjCPrimitiveType }
let sortedProps = self.properties.sorted { (arg1, _) in
arg1.1.schema.isObjCPrimitiveType
}

let propReturnStmts = sortedProps.map { param, prop -> String in
let formattedParam = param.snakeCaseToPropertyName()
Expand Down
3 changes: 2 additions & 1 deletion Sources/Core/ObjectiveCInitExtension.swift
Original file line number Diff line number Diff line change
Expand Up @@ -140,7 +140,8 @@ extension ObjCModelRenderer {
if let assignmentLine = lines.last {
let propAssignmentPrefix = "\(propertyToAssign) = "
if assignmentLine.hasPrefix(propAssignmentPrefix) {
let propertyInitStatement = assignmentLine.substring(from: propAssignmentPrefix.endIndex).trimmingCharacters(in: CharacterSet.init(charactersIn: " ;"))
let startIndex = propAssignmentPrefix.endIndex
let propertyInitStatement = String(assignmentLine[startIndex...]).trimmingCharacters(in: CharacterSet(charactersIn: " ;"))
let adtInitStatement = propAssignmentPrefix + "[\(adtClassName) objectWith\(ObjCADTRenderer.objectName(schema)):\(propertyInitStatement)];"
return lines.dropLast() + [adtInitStatement]
}
Expand Down
98 changes: 93 additions & 5 deletions Sources/Core/StringExtensions.swift
Original file line number Diff line number Diff line change
Expand Up @@ -52,31 +52,114 @@ prefix func --> (body: () -> [String]) -> String {
return -->body()
}

// Most of these are derived from https://www.binpress.com/tutorial/objective-c-reserved-keywords/43
// other language conflicts should be ideally added here.
// TODO: Find a way to separate this by language since the reserved keywords will differ.
let objectiveCReservedWordReplacements = [
"description": "description_text",
"id": "identifier"
// TODO: Fill out more objc keywords with replacements.
]

let objectiveCReservedWords = Set<String>([
"@catch()",
"@class",
"@dynamic",
"@end",
"@finally",
"@implementation",
"@interface",
"@private",
"@property",
"@protected",
"@protocol",
"@public",
"@selector",
"@synthesize",
"@throw",
"@try",
"BOOL",
"Class",
"IMP",
"NO",
"NULL",
"Protocol",
"SEL",
"YES",
"_Bool",
"_Complex",
"_Imaginery",
"atomic",
"auto",
"break",
"bycopy",
"byref",
"case",
"char",
"const",
"continue",
"default",
"do",
"double",
"else",
"enum",
"extern",
"float",
"for",
"goto",
"id",
"if",
"in",
"inline",
"inout",
"int",
"long",
"nil",
"nonatomic",
"oneway",
"out",
"register",
"restrict",
"retain",
"return",
"self",
"short",
"signed",
"sizeof",
"static",
"struct",
"super",
"switch",
"typedef",
"union",
"unsigned",
"void",
"volatile",
"while"
])

extension String {
/// All components separated by _ will be capitalized including the first one
func snakeCaseToCamelCase() -> String {
var str: String = self

if let replacementString = objectiveCReservedWordReplacements[self] as String? {
if let replacementString = objectiveCReservedWordReplacements[self.lowercased()] as String? {
str = replacementString
}

let components = str.components(separatedBy: "_")
let name = components.map { return $0.uppercaseFirst }
return name.joined(separator: "")
let formattedName = name.joined(separator: "")
if objectiveCReservedWords.contains(formattedName) {
return "\(formattedName)Property"
}
return formattedName
}

/// All components separated by _ will be capitalized execpt the first
func snakeCaseToPropertyName() -> String {
var str: String = self

if let replacementString = objectiveCReservedWordReplacements[self] as String? {
if let replacementString = objectiveCReservedWordReplacements[self.lowercased()] as String? {
str = replacementString
}

Expand All @@ -97,6 +180,10 @@ extension String {
}
}

if objectiveCReservedWords.contains(name) {
return "\(name)Property"
}

return name
}

Expand All @@ -108,7 +195,8 @@ extension String {

/// Get the last n characters of a string
func suffixSubstring(_ length: Int) -> String {
return self.substring(from: self.characters.index(self.endIndex, offsetBy: -length))
let index = self.characters.index(self.endIndex, offsetBy: -length)
return String(self[index...])
}

/// Uppercase the first character
Expand Down
5 changes: 2 additions & 3 deletions Tests/CoreTests/LinuxTestIndex.swift
Original file line number Diff line number Diff line change
Expand Up @@ -13,8 +13,6 @@ extension ObjectiveCIRTests {
static var allTests = [
("testDirtyPropertyOption", testDirtyPropertyOption),
("testDirtyPropertyOptionMultiWord", testDirtyPropertyOptionMultiWord),
("testDirtyPropertyOptionThrowsForEmptyProp", testDirtyPropertyOptionThrowsForEmptyProp),
("testDirtyPropertyOptionThrowsForEmptyClass", testDirtyPropertyOptionThrowsForEmptyClass),
("testEnumTypeName", testEnumTypeName),
("testEnumToStringName", testEnumToStringName),
("testEnumFromStringName", testEnumFromStringName),
Expand All @@ -37,7 +35,8 @@ extension StringExtensionsTests {
("testSuffixSubstring", testSuffixSubstring),
("testSnakeCaseToCamelCase", testSnakeCaseToCamelCase),
("testSnakeCaseToPropertyName", testSnakeCaseToPropertyName),
("testSnakeCaseToCapitalizedPropertyName", testSnakeCaseToCapitalizedPropertyName)
("testSnakeCaseToCapitalizedPropertyName", testSnakeCaseToCapitalizedPropertyName),
("testReservedKeywordSubstitution", testReservedKeywordSubstitution)
]
}
#endif
9 changes: 0 additions & 9 deletions Tests/CoreTests/ObjectiveCIRTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -20,15 +20,6 @@ class ObjectiveCIRTests: XCTestCase {
let optionName = dirtyPropertyOption(propertyName: "some_prop", className: "class")
XCTAssertEqual(optionName, "classDirtyPropertySomeProp")
}
// Figure out how to test fatalErrors or convert dirtyPropertyOption() to return a Result<String, Err> type
// func testDirtyPropertyOptionThrowsForEmptyProp() {
// XCTAssertThrowsError(dirtyPropertyOption(propertyName: "", className: "class"))
// }
//
// func testDirtyPropertyOptionThrowsForEmptyClass() {
//
// XCTAssertThrowsError(dirtyPropertyOption(propertyName: "", className: "class"))
// }

func testEnumTypeName() {
XCTAssertEqual(enumTypeName(propertyName: "some_prop", className: "class"), "classSomePropType")
Expand Down
4 changes: 4 additions & 0 deletions Tests/CoreTests/StringExtensionsTests.swift
Original file line number Diff line number Diff line change
Expand Up @@ -49,4 +49,8 @@ class StringExtensionsTests: XCTestCase {
XCTAssert("created_at".snakeCaseToCapitalizedPropertyName() == "CreatedAt")
XCTAssert("CreatedAt".snakeCaseToCapitalizedPropertyName() == "CreatedAt")
}

func testReservedKeywordSubstitution() {
XCTAssert("nil".snakeCaseToCapitalizedPropertyName() == "NilProperty")
}
}
21 changes: 13 additions & 8 deletions Utility/integration-test.sh
Original file line number Diff line number Diff line change
@@ -1,26 +1,31 @@
#!/bin/sh

set -euo pipefail

# Generate Objective-C files
JSON_FILES=`ls -d Examples/PDK/*.json`

# Generate Objective-C models
.build/debug/plank --output_dir=Examples/Cocoa/Sources/objc/ $JSON_FILES
.build/debug/plank --output_dir=Examples/Cocoa/Sources/Objective_C/ $JSON_FILES

# Generate flow types for models
.build/debug/plank --lang flow --output_dir=Examples/JS/flow/ $JSON_FILES

ROOT_DIR="${PWD}"

# Verify flow types
if [ -x "$(command -v flow)" ]; then
pushd Examples/JS/flow
cd Examples/JS/flow
flow
popd
cd "${ROOT_DIR}"
fi

# Move headers in the right place for the Swift PM
mv Examples/Cocoa/Sources/objc/*.h Examples/Cocoa/Sources/objc/include
mv Examples/Cocoa/Sources/Objective_C/*.h Examples/Cocoa/Sources/Objective_C/include

# Build the ObjC library
pushd Examples/Cocoa
swift build
swift test
popd
cd Examples/Cocoa
xcrun swift package clean
xcrun swift build
xcrun swift test
cd "${ROOT_DIR}"
Loading

0 comments on commit c5ed50a

Please sign in to comment.