Lint rules of hooks (#92)

* Remove broken test

* Add OneRenderFunctionRule

* Init RenderCorespondToNonPureComponentProtocolRule

* Fix OneRenderFunctionRule

* Apply swiftformat

* Fix RenderCorespondToNonPureComponentProtocolRule

* Add render check to RenderCorespondToNonPureComponentProtocolRule

* Update TokenVisitor

* Update getNodes

* Add TokenLint helpers

Add GetRender, IsConformance, TokenTypes

* Remove RenderCorespondToNonPureComponentProtocolRule

* Format comments

* Fix GetRender

* Remove TokenTypes

* Replace strings with SyntaxKind rawValue

* Add function to walk in graph

* Add TokenVisitor init function

* Add GraphWalkers to project

* Update isConformance function

* Update OneRenderFunctionRule

* Update getFirstChildOf function

* Update GraphWalkers

* Add getRender function

* Add RenderGetsHooksRule

* Update RenderGetsHooksRule

* Update RenderGetsHooksRule

* Apply swiftformat

* Rename getNodes to children

* Remove render from GetRender

* Remove IsConformance

* Mode nodes helpers to `Node.swift`

* Add test to Node

* Fix Unused Optional Binding Violation

* Fix Leading Whitespace Violation

* Apply swiftformat

* Fix typos

* Fix typos

* Remove unused code

* Clean code

* Move isInherited to Node

* Refactor hasTokamakImport

* Refactor Node

* Add helpers to TokamakCLITests

* Refactor code

* Update TokamakCLI main

* Refactor GetRender

* Fix typo

* Refactor rules

* Refactor TokenVIsitor

* Add TokamakCLI to .travis.yml

* Move NodeStruct to ValidationTests

* Add TEST_PATH to TokamakCLI

* Refactor StyleViolation

* Refactor Rule

* Add SwiftCLI to Package

* Apply swiftformat

* Regenerate project

* Update project.pbxproj

* Remove package for swift-4.2
This commit is contained in:
matvii 2019-05-14 10:12:47 +03:00 committed by GitHub
parent 78fa74d592
commit 4f02218fdb
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
21 changed files with 5502 additions and 3143 deletions

View File

@ -7,37 +7,31 @@ language: swift
jobs:
include:
- stage: lint
osx_image: xcode10.2
language: swift
before_install:
- brew install swiftformat
script:
# temporarily disabled due to Swift ABI issues on Xcode 10.2, Mojave 10.14.4
- swiftformat --lint --verbose .
- swiftlint
- &test
stage: test
osx_image: xcode10
language: swift
install: skip
env: TEST_DEVICE='platform=iOS Simulator,OS=12.0,name=iPhone SE'
script:
- xcodebuild -scheme TokamakUIKit -sdk iphonesimulator | xcpretty
- xcodebuild -scheme TokamakAppKit -sdk macosx10.14 | xcpretty
# this runs the build the second time, but it's the only way to make sure
# that `Package.swift` is in a good state
- swift build --target Tokamak
- xcodebuild test -enableCodeCoverage YES -scheme Tokamak | xcpretty
- <<: *test
osx_image: xcode10.1
env: TEST_DEVICE='platform=iOS Simulator,OS=12.1,name=iPhone SE'
- <<: *test
osx_image: xcode10.2
env: TEST_DEVICE='platform=iOS Simulator,OS=12.2,name=iPhone SE'
after_success:
- bash <(curl -s https://codecov.io/bash)
- stage: lint
osx_image: xcode10.2
language: swift
before_install:
- brew install swiftformat
script:
# temporarily disabled due to Swift ABI issues on Xcode 10.2, Mojave 10.14.4
- swiftformat --lint --verbose .
- swiftlint
- &test
stage: test
osx_image: xcode10.2
language: swift
install: skip
env: TEST_DEVICE='platform=iOS Simulator,OS=12.2,name=iPhone SE'
script:
- xcodebuild -scheme TokamakUIKit -sdk iphonesimulator | xcpretty
- xcodebuild -scheme TokamakAppKit -sdk macosx | xcpretty
- xcodebuild test -scheme TokamakCLI -sdk macosx | xcpretty
# this runs the build the second time, but it's the only way to make sure
# that `Package.swift` is in a good state
- swift build --target Tokamak
- xcodebuild test -enableCodeCoverage YES -scheme Tokamak | xcpretty
after_success:
- bash <(curl -s https://codecov.io/bash)
# before_install:
# - gem install cocoapods --pre # Since Travis is not always on latest version
# - brew outdated carthage || brew upgrade carthage

View File

@ -1,81 +0,0 @@
// swift-tools-version:4.2
// The swift-tools-version declares the minimum version of Swift required to
// build this package.
import PackageDescription
let package = Package(
name: "Tokamak",
products: [
// Products define the executables and libraries produced by a package,
// and make them visible to other packages.
.library(
name: "Tokamak",
targets: ["Tokamak"]
),
.library(
name: "TokamakUIKit",
targets: ["TokamakUIKit"]
),
.library(
name: "TokamakAppKit",
targets: ["TokamakAppKit"]
),
.library(
name: "TokamakTestRenderer",
targets: ["TokamakTestRenderer"]
),
.library(
name: "TokamakLint",
targets: ["TokamakLint"]
),
.executable(name: "tokamak", targets: ["TokamakCLI"]),
],
dependencies: [
// Dependencies declare other packages that this package depends on.
// .package(url: /* package url */, from: "1.0.0"),
.package(url: "https://github.com/apple/swift-syntax.git", .exact("0.50000.0")),
],
targets: [
// Targets are the basic building blocks of a package. A target can define
// a module or a test suite.
// Targets can depend on other targets in this package, and on products
// in packages which this package depends on.
.target(
name: "Tokamak",
dependencies: []
),
.target(
name: "TokamakDemo",
dependencies: ["Tokamak"]
),
.target(
name: "TokamakUIKit",
dependencies: ["Tokamak"]
),
.target(
name: "TokamakAppKit",
dependencies: ["Tokamak"]
),
.target(
name: "TokamakTestRenderer",
dependencies: ["Tokamak"]
),
.target(
name: "TokamakCLI",
dependencies: ["TokamakLint"]
),
.target(
name: "TokamakLint",
dependencies: ["SwiftSyntax"]
),
.testTarget(
name: "TokamakTests",
dependencies: ["TokamakTestRenderer"]
),
.testTarget(
name: "TokamakCLITests",
dependencies: ["TokamakLint"]
),
]
)

View File

@ -36,4 +36,4 @@ class LintCommand: Command {
let TokamakCLI = CLI(name: "TokamakCLI", version: "0.1.2", description: "Tokamak CLI tools")
TokamakCLI.commands = [LintCommand()]
TokamakCLI.go()
TokamakCLI.goAndExit()

View File

@ -0,0 +1,65 @@
//
// GetRender.swift
// TokamakLint
//
// Created by Matvii Hodovaniuk on 5/8/19.
//
import Foundation
import SwiftSyntax
extension Array: Error where Element == StyleViolation {}
func getRender(from node: Node, at file: String) throws -> Node {
let renders = node.children(with: "render").filter {
// check if render type is function
var memberDeclListItem = $0
while memberDeclListItem.text != SyntaxKind.memberDeclListItem.rawValue
&& memberDeclListItem.parent != nil {
guard let parent = memberDeclListItem.parent else { break }
memberDeclListItem = parent
}
guard let functionDecl = memberDeclListItem.children.first,
functionDecl.text == SyntaxKind.functionDecl.rawValue else { return false }
// check if render is static
let staticModifier = memberDeclListItem.children(
with: SyntaxKind.declModifier.rawValue
).filter {
guard let child = $0.children.first else { return false }
return child.text == "static"
}
guard staticModifier.first != nil else { return false }
// check if render is on first layer of component
return functionDecl.children.map {
$0.text
}.contains("render")
}
guard renders.count == 1 else {
if renders.count > 1 {
throw renders.map {
StyleViolation(
ruleDescription: OneRenderFunctionRule.description,
location: Location(
file: file,
line: $0.range.startRow,
character: $0.range.startColumn
)
)
}
} else {
throw [StyleViolation(
ruleDescription: OneRenderFunctionRule.description,
location: Location(
file: file,
line: node.range.startRow,
character: node.range.startColumn
)
)]
}
}
return renders[0]
}

View File

@ -14,7 +14,7 @@ public struct Location: CustomStringConvertible, Comparable {
public let character: Int
public var description: String {
// Xcode likes warnings and errors in the following format:
// {full_path_to_file}{:line}{:character}: {error,warning}: {content}
// {full_path_to_file}{:line}{:character}: {error, warning}: {content}
return "\(file):\(line):\(character)"
}

View File

@ -0,0 +1,79 @@
//
// Node.swift
// TokamakLint
//
// Created by Matvii Hodovaniuk on 5/11/19.
//
import Foundation
import SwiftSyntax
public class Node {
var text: String
private(set) var children = [Node]()
private(set) weak var parent: Node?
var range = Range(startRow: 0, startColumn: 0, endRow: 0, endColumn: 0)
struct Range {
var startRow: Int
var startColumn: Int
var endRow: Int
var endColumn: Int
}
init(text: String) {
self.text = text
}
func add(node: Node) {
node.parent = self
children.append(node)
}
func children(with type: String) -> [Node] {
guard children.first != nil else { return [] }
var nodes: [Node] = []
walkAndGrab(get: type, from: self, to: &nodes)
return nodes
}
func walkAndGrab(
get type: String,
from node: Node,
to list: inout [Node]
) {
if node.text == type {
list.append(node)
}
for child in node.children {
walkAndGrab(get: type, from: child, to: &list)
}
}
func firstParent(of type: String) -> Node? {
var nodeParent = self
while nodeParent.text != type && nodeParent.parent != nil {
guard let parent = nodeParent.parent else { break }
nodeParent = parent
}
guard nodeParent.text == type else { return nil }
return nodeParent
}
func firstChild(of type: String) -> Node? {
guard !children.isEmpty else { return nil }
return children.first { $0.text == type } ??
children.compactMap { $0.firstChild(of: type) }.first
}
func isInherited(from type: String) -> Bool {
let typeNodes = children(
with: SyntaxKind.simpleTypeIdentifier.rawValue
)
return typeNodes.flatMap { $0.children }.contains { $0.text == type }
}
}

View File

@ -56,8 +56,7 @@ func checkFile(_ path: String) throws -> [StyleViolation] {
private func walkParsedTree(_ path: String) throws -> TokenVisitor {
let fileURL = URL(fileURLWithPath: path)
let parsedTree = try SyntaxTreeParser.parse(fileURL)
let visitor = TokenVisitor()
visitor.path = path
let visitor = TokenVisitor(path: path)
parsedTree.walk(visitor)
return visitor
}
@ -67,14 +66,8 @@ private func isSwiftFile(_ path: String) -> Bool {
}
private func hasTokamakImport(from visitor: TokenVisitor) -> Bool {
var doesTokamakImportExist = false
let imports = visitor.getNodes(get: "ImportDecl", from: visitor.tree[0])
for importNode in imports {
let importModules = visitor.getNodes(get: "AccessPathComponent", from: importNode)
for module in importModules where module.children[0].text == "Tokamak" {
doesTokamakImportExist = true
}
}
return doesTokamakImportExist
return visitor.root.children(with: SyntaxKind.importDecl.rawValue)
.flatMap { $0.children(with: SyntaxKind.accessPathComponent.rawValue) }
.compactMap { $0.children.first }
.contains { $0.text == "Tokamak" }
}

View File

@ -9,14 +9,19 @@ import Foundation
import SwiftSyntax
class TokenVisitor: SyntaxVisitor {
public var tree = [Node]()
public var path: String?
public var current: Node?
// Syntax tree is always has one 'SourceFile' node as a child
var root = Node(text: "Root")
var path: String
private var current: Node?
init(path: String) {
self.path = path
}
var row = 0
var column = 0
public override func visitPre(_ node: Syntax) {
override func visitPre(_ node: Syntax) {
var syntax = "\(type(of: node))"
if syntax.hasSuffix("Syntax") {
@ -28,12 +33,12 @@ class TokenVisitor: SyntaxVisitor {
if let current = current {
current.add(node: syntaxNode)
} else {
tree.append(syntaxNode)
root.add(node: syntaxNode)
}
current = syntaxNode
}
public override func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
override func visit(_ token: TokenSyntax) -> SyntaxVisitorContinueKind {
guard let current = current else { return .visitChildren }
current.text = token.text
@ -65,7 +70,7 @@ class TokenVisitor: SyntaxVisitor {
return .visitChildren
}
public override func visitPost(_ node: Syntax) {
override func visitPost(_ node: Syntax) {
// go up after full node walk
current = current?.parent
}
@ -105,64 +110,4 @@ class TokenVisitor: SyntaxVisitor {
row += comments.count - 1
column += last.count
}
public func getNodes(get type: String, from node: Node) -> [Node] {
var nodes: [Node] = []
walkAndGrab(get: type, from: node, to: &nodes)
return nodes
}
private func walkAndGrab(
get type: String,
from node: Node,
to list: inout [Node]
) {
if node.text == type {
list.append(node)
}
for child in node.children {
walkAndGrab(get: type, from: child, to: &list)
}
}
func isInherited(node: Node, from type: String) -> Bool {
var str: String = ""
let typeNodes = getNodes(get: "SimpleTypeIdentifier", from: node)
for node in typeNodes {
for type in node.children {
str.append("\(type.text)")
}
}
return str.contains(type)
}
}
public class Node {
var text: String
var children = [Node]()
weak var parent: Node?
var range = Range(startRow: 0, startColumn: 0, endRow: 0, endColumn: 0)
struct Range: Encodable {
var startRow: Int
var startColumn: Int
var endRow: Int
var endColumn: Int
}
enum CodingKeys: CodingKey {
case text
case children
case range
case token
}
init(text: String) {
self.text = text
}
func add(node: Node) {
node.parent = self
children.append(node)
}
}

View File

@ -14,11 +14,10 @@ protocol Rule {
}
extension Rule {
public static func validate(path: String) throws -> [StyleViolation] {
static func validate(path: String) throws -> [StyleViolation] {
let fileURL = URL(fileURLWithPath: path)
let parsedTree = try SyntaxTreeParser.parse(fileURL)
let visitor = TokenVisitor()
visitor.path = path
let visitor = TokenVisitor(path: path)
parsedTree.walk(visitor)
return validate(visitor: visitor)
}

View File

@ -0,0 +1,28 @@
//
// OneRenderFunctionRule.swift
// TokamakCLI
//
// Created by Matvii Hodovaniuk on 5/6/19.
//
import SwiftSyntax
struct OneRenderFunctionRule: Rule {
static let description = RuleDescription(
type: OneRenderFunctionRule.self,
name: "One Render Function",
description: "The component should have only one render function"
)
static func validate(visitor: TokenVisitor) -> [StyleViolation] {
do {
_ = try getRender(from: visitor.root, at: visitor.path)
} catch let error as [StyleViolation] {
return error
} catch {
print(error)
return []
}
return []
}
}

View File

@ -5,19 +5,18 @@
// Created by Matvii Hodovaniuk on 4/9/19.
//
import Foundation
import SwiftSyntax
struct PropsIsEquatableRule: Rule {
public static let description = RuleDescription(
static let description = RuleDescription(
type: PropsIsEquatableRule.self,
name: "Props is Equatable",
description: "Component Props type shoud conformance to Equatable protocol"
description: "Component Props type should conform to Equatable protocol"
)
public static func validate(visitor: TokenVisitor) -> [StyleViolation] {
static func validate(visitor: TokenVisitor) -> [StyleViolation] {
var violations: [StyleViolation] = []
let structs = visitor.getNodes(get: "StructDecl", from: visitor.tree[0])
let structs = visitor.root.children(with: SyntaxKind.structDecl.rawValue)
for structNode in structs {
// sometimes there are additional children `ModifierList`
// it will be better to filter array to find out if struct name is
@ -28,15 +27,14 @@ struct PropsIsEquatableRule: Rule {
guard propsNodes.count != 0 else { continue }
let propsNode = propsNodes[0]
guard let propsParent = propsNode.parent, !visitor.isInherited(
node: propsParent,
guard let propsParent = propsNode.parent, !propsParent.isInherited(
from: "Equatable"
) else { continue }
violations.append(
StyleViolation(
ruleDescription: description,
location: Location(
file: visitor.path ?? "",
file: visitor.path,
line: propsNode.range.startRow,
character: propsNode.range.startColumn
)

View File

@ -0,0 +1,60 @@
//
// RenderGetsHooksRule.swift
// TokamakLint
//
// Created by Matvii Hodovaniuk on 5/6/19.
//
import SwiftSyntax
struct RenderGetsHooksRule: Rule {
static let description = RuleDescription(
type: RenderGetsHooksRule.self,
name: "Render gets Hooks",
description: "Non-pure render should get Hooks type argument"
)
public static func validate(visitor: TokenVisitor) -> [StyleViolation] {
do {
let renderFunction = try getRender(from: visitor.root, at: visitor.path)
guard let codeBlock = renderFunction.firstParent(
of: SyntaxKind.codeBlockItem.rawValue
) else { return [StyleViolation(
ruleDescription: OneRenderFunctionRule.description,
location: Location(
file: visitor.path,
line: renderFunction.range.startRow,
character: renderFunction.range.startColumn
)
)] }
guard let functionSignature = codeBlock.firstChild(
of: SyntaxKind.functionSignature.rawValue
) else { return [] }
let hooksArgument = functionSignature.children(
with: SyntaxKind.simpleTypeIdentifier.rawValue
).filter {
guard let children = $0.children.first else { return false }
return children.text == "Hooks"
}
guard !hooksArgument.isEmpty else {
return [StyleViolation(
ruleDescription: OneRenderFunctionRule.description,
location: Location(
file: visitor.path,
line: renderFunction.range.startRow,
character: renderFunction.range.startColumn
)
)]
}
} catch let error as [StyleViolation] {
return error
} catch {
print(error)
return []
}
return []
}
}

View File

@ -0,0 +1,17 @@
//
// Helpers.swift
// TokamakCLITests
//
// Created by Matvii Hodovaniuk on 5/13/19.
//
import XCTest
struct UnexpectedNilError: Error {}
func srcRoot() throws -> String {
guard let src = ProcessInfo.processInfo.environment["TEST_PATH"] else {
throw UnexpectedNilError()
}
return src
}

View File

@ -0,0 +1,73 @@
//
// NodeTests.swift
// TokamakLint
//
// Created by Matvii Hodovaniuk on 5/11/19.
//
import SwiftSyntax
@testable import TokamakLint
import XCTest
final class NodeTests: XCTestCase {
func testCollectChildren() throws {
let houseLannister = Node(text: "Tywin")
houseLannister.add(node: Node(text: "Jaime"))
houseLannister.add(node: Node(text: "Cersei"))
houseLannister.add(node: Node(text: "Tyrion"))
XCTAssertEqual(houseLannister.children(with: "Jaime").count, 1)
XCTAssertEqual(houseLannister.children(with: "JonSnow").count, 0)
let cerseiChildren = Node(text: "Cersei")
let joffrey = Node(text: "Joffrey")
joffrey.add(node: Node(text: "Father: Jaime"))
cerseiChildren.add(node: joffrey)
let myrcella = Node(text: "Myrcella")
myrcella.add(node: Node(text: "Father: Jaime"))
cerseiChildren.add(node: myrcella)
let tommen = Node(text: "Tommen")
tommen.add(node: Node(text: "Father: Jaime"))
cerseiChildren.add(node: tommen)
XCTAssertEqual(cerseiChildren.children(with: "Father: Jaime").count, 3)
XCTAssertEqual(cerseiChildren.children(with: "Father: Robert").count, 0)
}
func testFirstParent() throws {
let houseTargaryen = Node(text: "House Targryen")
let aerys = Node(text: "Aerys \"the Mad\"")
houseTargaryen.add(node: aerys)
let rhaegar = Node(text: "Rhaegar")
aerys.add(node: rhaegar)
let jonSnow = Node(text: "Jon Snow")
rhaegar.add(node: jonSnow)
guard let madKing = jonSnow.firstParent(of: "Aerys \"the Mad\"") else {
throw UnexpectedNilError()
}
XCTAssertEqual(madKing.text, "Aerys \"the Mad\"")
}
func testFirstChild() throws {
let path = "\(try srcRoot())/NodeStruct.swift"
let fileURL = URL(fileURLWithPath: path)
let parsedTree = try SyntaxTreeParser.parse(fileURL)
let visitor = TokenVisitor(path: path)
parsedTree.walk(visitor)
guard let firstStruct = visitor.root.firstChild(
of: SyntaxKind.structDecl.rawValue
)
else { throw UnexpectedNilError() }
XCTAssertEqual(firstStruct.children[1].text, "Img")
}
}

View File

@ -8,24 +8,28 @@
@testable import TokamakLint
import XCTest
let srcRoot = ProcessInfo.processInfo.environment["SRCROOT"]!
final class TokamakLintTests: XCTestCase {
func testFile() throws {
let path = "\(srcRoot)/Sources/Tokamak/Components/Host/Button.swift"
let result = try lintFile(path)
XCTAssertEqual(result, [])
}
func testPositivePropsIsEquatableRule() throws {
let path = "\(srcRoot)/ValidationTests/TestPropsEquatable.swift"
let path = "\(try srcRoot())/TestPropsEquatable.swift"
let result = try PropsIsEquatableRule.validate(path: path)
XCTAssertEqual(result, [])
}
func testNegativePropsIsEquatableRule() throws {
let path = "\(srcRoot)/ValidationTests/TestPropsIsNotEquatable.swift"
let path = "\(try srcRoot())/TestPropsIsNotEquatable.swift"
let result = try PropsIsEquatableRule.validate(path: path)
XCTAssertEqual(result.count, 1)
}
func testOneRenderFunctionRule() throws {
let path = "\(try srcRoot())/PositiveTestHooksRule.swift"
let result = try OneRenderFunctionRule.validate(path: path)
XCTAssertEqual(result, [])
}
func testRenderGetsHooksRule() throws {
let path = "\(try srcRoot())/PositiveTestHooksRule.swift"
let result = try RenderGetsHooksRule.validate(path: path)
XCTAssertEqual(result, [])
}
}

View File

@ -11,21 +11,19 @@ import XCTest
final class TokenVisitorTests: XCTestCase {
func testRange() throws {
let srcRoot = ProcessInfo.processInfo.environment["SRCROOT"]!
let path = "\(srcRoot)/ValidationTests/TestPropsEquatable.swift"
let path = "\(try srcRoot())/TestPropsEquatable.swift"
let fileURL = URL(fileURLWithPath: path)
let parsedTree = try SyntaxTreeParser.parse(fileURL)
let visitor = TokenVisitor()
let visitor = TokenVisitor(path: path)
visitor.path = path
parsedTree.walk(visitor)
let startRow = 11
let endRow = 11
let startColumn = 0
let endColumn = 7
let structs = visitor.getNodes(get: "StructDecl", from: visitor.tree[0])
let structs = visitor.root.children(with: "StructDecl")
let structDecl = structs[0].children[0]
XCTAssertEqual(startRow, structDecl.range.startRow)

File diff suppressed because it is too large Load Diff

View File

@ -14,9 +14,23 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Tokamak::TokamakDemo"
BuildableName = "TokamakDemo.framework"
BlueprintName = "TokamakDemo"
BlueprintIdentifier = "Tokamak::TokamakUIKit"
BuildableName = "TokamakUIKit.framework"
BlueprintName = "TokamakUIKit"
ReferencedContainer = "container:Tokamak.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Tokamak::Tokamak"
BuildableName = "Tokamak.framework"
BlueprintName = "Tokamak"
ReferencedContainer = "container:Tokamak.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@ -62,20 +76,6 @@
ReferencedContainer = "container:Tokamak.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
buildForProfiling = "YES"
buildForArchiving = "YES"
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Tokamak::TokamakUIKit"
BuildableName = "TokamakUIKit.framework"
BlueprintName = "TokamakUIKit"
ReferencedContainer = "container:Tokamak.xcodeproj">
</BuildableReference>
</BuildActionEntry>
<BuildActionEntry
buildForTesting = "YES"
buildForRunning = "YES"
@ -98,9 +98,9 @@
buildForAnalyzing = "YES">
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Tokamak::Tokamak"
BuildableName = "Tokamak.framework"
BlueprintName = "Tokamak"
BlueprintIdentifier = "Tokamak::TokamakDemo"
BuildableName = "TokamakDemo.framework"
BlueprintName = "TokamakDemo"
ReferencedContainer = "container:Tokamak.xcodeproj">
</BuildableReference>
</BuildActionEntry>
@ -149,9 +149,9 @@
<MacroExpansion>
<BuildableReference
BuildableIdentifier = "primary"
BlueprintIdentifier = "Tokamak::TokamakDemo"
BuildableName = "TokamakDemo.framework"
BlueprintName = "TokamakDemo"
BlueprintIdentifier = "Tokamak::TokamakUIKit"
BuildableName = "TokamakUIKit.framework"
BlueprintName = "TokamakUIKit"
ReferencedContainer = "container:Tokamak.xcodeproj">
</BuildableReference>
</MacroExpansion>

View File

@ -64,8 +64,8 @@
</BuildableProductRunnable>
<EnvironmentVariables>
<EnvironmentVariable
key = "SRCROOT"
value = "${SRCROOT}"
key = "TEST_PATH"
value = "${SRCROOT}/ValidationTests"
isEnabled = "YES">
</EnvironmentVariable>
</EnvironmentVariables>

View File

@ -0,0 +1,25 @@
//
// NodeStruct.swift
// SwiftCLI
//
// This file help to test Node
//
// Created by Matvii Hodovaniuk on 5/12/19.
//
import Foundation
struct Img {
let className = "Icon"
let id = "uniq-id-0001"
let height = 400
let width = 300
}
struct Div {
let className = "Avatar"
let id = "uniq-id-0000"
let disabled = false
let content = Img()
}

View File

@ -0,0 +1,62 @@
//
// TextField.swift
// TokamakDemo
//
// Created by Matvii Hodovaniuk on 2/25/19.
// Copyright © 2019 Tokamak. Tokamak is available under the Apache 2.0
// license. See the LICENSE file for more info.
//
import Tokamak
struct TextFieldExample: LeafComponent {
typealias Props = Null
static func render(props: Props, hooks: Hooks) -> AnyNode {
let text = hooks.state("")
let textFieldStyle = Style(
[
Height.equal(to: 44),
Width.equal(to: .parent),
]
)
return StackView.node(.init(
[
Leading.equal(to: .safeArea),
Trailing.equal(to: .safeArea),
Top.equal(to: .safeArea),
],
alignment: .top,
axis: .vertical
), [
TextField.node(.init(
textFieldStyle,
placeholder: "Default",
value: text.value,
valueHandler: Handler(text.set)
)),
TextField.node(.init(
textFieldStyle,
isEnabled: false,
placeholder: "Disabled",
value: text.value,
valueHandler: Handler(text.set)
)),
TextField.node(.init(
textFieldStyle,
keyboardAppearance: .dark,
placeholder: "Dark",
value: text.value,
valueHandler: Handler(text.set)
)),
TextField.node(.init(
textFieldStyle,
isSecureTextEntry: true,
placeholder: "Password",
value: text.value,
valueHandler: Handler(text.set)
)),
])
}
}