Swift-Lint Danger on Bitrise

Akarapas Wongkaew
20Scoops CNX

--

กราบสวัสดีพ่อแม่พี่น้องที่หลงเข้ามาอ่านบทความแรกของผมด้วยนะครับ ในวันนี้เราจะมาพูดถึง lint ในภาษา Swift และ Danger กัน ว่าคืออะไร ใช้งานอย่างไร น่าสนใจแค่ไหน หลายๆคนคงเริ่มอยากจะรู้กันแล้ว ถ้างั้นเรามาเริ่มกันเลยย~

รู้จักกันก่อน Lint คืออะไร

ขอยกคำพูดของเจ้าของบทความมาเลยละกันนะครับ

Lint คือ Static code analysis เป็นเครื่องมือหนึ่งที่ช่วยการวิเคราะห์โครงสร้างของโค้ดและช่วยแนะนำวิธีการปรับปรุงที่มาพร้อมกับคำอธิบายมีให้ใช้ทุกภาษานะครับ

สามารถอ่านเพิ่มเติมได้ที่ มารู้จักกับ Android Lint — Jedsada Tiwongvorakul

Danger คืออะไร

นั่นสิ Danger คืออะไรกันนะ ไปอ่านเจอข้อความหนึ่งที่พูดถึง Danger ว่า

Danger runs after your CI, automating your team’s conventions surrounding code review.
This provides another logical step in your process, through this Danger can help lint your rote tasks in daily code review.
You can use Danger to codify your team’s norms, leaving humans to think about harder problems.

เอาเป็นว่าขอสรุปตามความเข้าใจของตัวเองก็แล้วกันนะครับ

Danger คือเครื่องมือหนึ่งที่ช่วยจัดการเรื่องการตรวจทานโค้ดให้อัตโนมัติตามมาตรฐานของทีมที่สร้างขึ้น

อีกทั้ง Danger ยังสามารถทำงานร่วมกับ lint ได้ด้วยนะ So cool ~

น่าจะยังงงๆกันอยู่ใช่ไหม

เริ่มต้นยังไง

สำหรับคนที่ใช้ fastlane (CI/CD tools) ในการทำงานอยู่แล้ว บอกเลยว่าง่ายมากครับ แต่ใครที่ยังไม่เคยใช้หรือกำลังจะเริ่มหัดใช้ แนะนำให้ใช้นะครับ เพราะจะทำให้ชีวิตง่ายขึ้นเยอะ

เตรียม Gemfile, Fastfile, Dangerfile และ .swiftlint.ymlให้พร้อม

ในส่วนของ Gemfile เป็นไฟล์ที่ใช้สำหรับบอก Dependency version เช่น Library, Ruby Package ในภาษา Ruby ที่เราต้องการใช้ในโปรเจคของเรา ให้ทำการเพิ่ม gem ‘danger’ สำหรับการใช้งาน Danger tool และ gem ‘danger-checkstyle_format’, ‘~> 0.1.1’ เป็น plugin ของ Danger tool ที่ใช้สำหรับการอ่านไฟล์ Report จากคำสั่ง swiftlint ใน fastlane เพื่อเอาผลลัพธ์ที่ได้ไป Comment แบบ Inline Code บน GitHub นั่นเอง

สามารถ copy code ตัวอย่างด้านล่างไปใส่ใน TextEditor (เช่น Sublime) แล้ว Save เป็นชื่อ Gemfile ได้เลยย~

ตัวอย่าง Gemfile

source “https://rubygems.org”
gem ‘danger’
gem ‘danger-checkstyle_format’, ‘~> 0.1.1’

ในส่วนของ Fastfile เป็นไฟล์ที่ใช้สำหรับสร้างขั้นตอนการทำงานต่างๆให้กับ fastlane สามารถแบ่งออกเป็นหลายๆ lane ได้ ให้ทำการเพิ่ม lane สำหรับการใช้ lint ผ่านคำสั่ง swiftlint เข้าไป โดย Fastfile นี้เขียนด้วยภาษา Ruby นะ

ตัวอย่าง Fastfile

fastlane_version "2.3.12"default_platform :iosEncoding.default_external = Encoding::UTF_8
Encoding.default_internal = Encoding::UTF_8
platform :ios dobefore_all do

end
desc "Check and Generate swiftlint.result.xml report lint file"
lane :checking_by_lint do
swiftlint(
mode: :lint, # SwiftLint mode: :lint (default) or :autocorrect
output_file: "./swiftlint.result.xml", # The path of the output file (optional)
reporter: "checkstyle", # The custom reporter to use (optional)
config_file: ".swiftlint.yml", # The path of the configuration file (optional)
ignore_exit_status: true, # Allow fastlane to continue even if SwiftLint returns a non-zero exit status (Default: false)
quiet: true, # Don't print status logs like 'Linting ' & 'Done linting' (Default: false)
strict: false # Fail on warnings? (Default: false)
)
endafter_all do |lane|
# This block is called, only if the executed lane was successful
end
error do |lane, exception|
#slack(
# message: exception.message,
# success: false
#)
end
end

สำหรับการตั้งค่าของคำสั่ง swiftlint สามารถดูได้จาก fastlane/swiftlint

ในส่วนของ Dangerfile เป็นไฟล์ที่ใช้สำหรับตั้งค่าการทำงานของ Danger รวมถึงตั้งค่า/เรียกใช้ plugin ต่างๆของ Danger นั้น ยังสามารถกำหนด rule สำหรับการ PR (Pull Request) และแสดงผลลัพธ์ผ่าน Comment ใน GitHub ได้อีกด้วย

สามารถดูคำสั่งต่างๆของ Danger ได้ที่ danger.systems

ตัวอย่าง Dangerfile

# Ignore inline messages which lay outside a diff’s range of PR
github.dismiss_out_of_range_messages
# Make it more obvious that a PR is a work in progress and shouldn’t be merged yet
warn(“PR is classed as Work in Progress”) if github.pr_title.include? “[WIP]” or github.pr_labels.include?(“WIP”)
# Warn when there is a big PR
warn(“Big PR”) if git.lines_of_code > 300
warn(“Large PR”) if git.lines_of_code > 500
warn(“Huge PR”) if git.lines_of_code > 700
warn(“Freakin Huge PR”) if git.lines_of_code > 1000
# lint
checkstyle_format.base_path = Dir.pwd
checkstyle_format.report ‘swiftlint.result.xml’

ในส่วนของ .swiftlint.yml เป็นไฟล์ที่ใช้สำหรับการตั้งค่าและกำหนดเงื่อนไขสำหรับการทำงานของคำสั่ง swiftlint

สามารถศึกษารายละเอียดคำสั่งต่างๆของ swiftlint ได้ที่ realm/SwiftLint

ตัวอย่าง .swiftlint.yml

disabled_rules: # rule identifiers to exclude from running
# — colon
# — comma
# — control_statement
— cyclomatic_complexity
opt_in_rules: # some rules are only opt-in
# — empty_count
# Find all the available rules by running:
# swiftlint rules
included: # paths to include during linting. ` — path` is ignored if present.
— SwiftLintDanger
excluded: # paths to ignore during linting. Takes precedence over `included`.
— Carthage
— Pods
— Source/ExcludedFolder
— Source/ExcludedFile.swift
— Source/*/ExcludedFile.swift # Exclude files with a wildcard
analyzer_rules: # Rules run by `swiftlint analyze` (experimental)
— explicit_self
# configurable rules can be customized from this configuration file
# binary rules can set their severity level
force_cast: warning # implicitly
force_try:
severity: warning # explicitly
# rules that have both warning and error levels, can set just the warning level
# implicitly
line_length: 350
# they can set both implicitly with an array
type_body_length:
— 300 # warning
— 400 # error
# or they can set both explicitly
file_length:
warning: 500
error: 1200
# naming rules can set warnings/errors for min_length and max_length
# additionally they can set excluded names
type_name:
min_length: 1 # only warning
max_length: # warning and error
warning: 40
error: 50
excluded: iPhone # excluded via string
identifier_name:
min_length: # only min_length
error: 3 # only error
excluded: # excluded via string array
— id
— URL
— GlobalAPIKey
reporter: “checkstyle” # reporter type (xcode, json, csv, checkstyle, junit, html, emoji, sonarqube, markdown)

หลังจากเตรียม Gemfile, Fastfile, Dangerfile และ .swiftlint.yml เรียบร้อยแล้ว ก็ทำ push code ขึ้น Repository ได้เลย โดยในบทความนี้ผมจะใช้ Repository ของ GitHub นะครับ

ตัวอย่างไฟล์ใน Folder ของ Project

ในส่วนของ Podfile นั้นจะคล้ายๆกับ Gemfile แตกต่างกันตรงที่จะเรียกใช้ dependency จากคนละที่กัน ซึ่ง Podfile จะเรียกผ่าน https://cocoapods.org ส่วน Gemfile จะเรียกผ่าน https://rubygems.org

มาถึงตรงนี้ หาก Finder ท่านใดมองไม่เห็น hidden files ให้ทำตามนี้ได้เลยครับ Show/Hide Hidden Files on macOS

ตั้งค่า CI

ในบทความนี้ผมจะใช้ CI ของ bitrise เนื่องจากมีรูปแบบการใช้งานที่ง่าย และ UI ดูเป็นมิตรกับผู้ใช้ครับ และที่สำคัญคือ Free(mium) !! สำหรับคนที่ยังไม่เคยลองใช้นั้น ลองสักครั้งแล้วจะติดใจนะ อิอิ

CI ที่เราจะใช้กัน https://www.bitrise.io

สำหรับคนที่ใช้ GitHub อยู่แล้ว และ ยังไม่ได้สมัครกับ bitrise วิธีการง่ายนิดเดียวครับ แค่กดปุ่ม Sign Up ด้านขวาบน และ กดปุ่ม Sign up with GitHub หลังจากนั้นก็ตาม Step การสมัครทั่วไปครับ จะให้กรอก Username, Password และข้อมูลส่วนตัวอีกนิดๆหน่อยๆ ก็เป็นอันจบพิธี

Sign up with GitHub ได้เลยจ้าา

หลังจากสมัครเสร็จเรียบร้อยแล้ว หน้า Dashboard ของเราก็จะเป็นประมาณนี้

bitrise Dashboard

ให้ทำการเพิ่ม Project เข้าไปใน bitrise โดยกดที่ Add your first app หรือสำหรับคนที่ใช้ bitrise อยู่แล้ว ก็จะมีปุ่ม Add New App ในหน้า Dashboard ครับ

Create New App

เมื่อทำตาม step การ create new app เรียบร้อยแล้ว bitrise จะทำการ setup ทั้ง Deploy Keys, Webhooks ใน GitHub ให้เลย เป็นอะไรที่สะดวกมากครับ

หลังจากนั้นให้ทำการแก้ไข bitrise.yml โดยกดเข้าไปที่ Project > Workflow > bitrise.yml แล้วเพิ่ม code ดังต่อไปนี้ด้านล่าง workflows:

SwiftLintDanger:steps:- activate-ssh-key:run_if: ‘{{getenv “SSH_RSA_PRIVATE_KEY” | ne “”}}’- git-clone: {}- script:run_if: “.IsPR”inputs:- is_debug: ‘yes’- content: |-#!/bin/bashset -exbrew install swiftlintbundle install- fastlane:inputs:- lane: ios checking_by_lint- script:run_if: “.IsPR”inputs:- is_debug: ‘yes’- content: |-#!/bin/bashset -ex# Running Dangerbundle exec danger

หรือจะทำการเพิ่ม Workflow เองผ่านทาง Workflows ของ bitrise ก็ได้ครับ

Workflows UI

ขั้นตอนการทำงานที่เราจะใช้คือ

  1. Activate SSH Key
  2. Git Clone Repository
  3. brew install swiftlint
  4. bundle install
  5. fastlane ios checking_by_lint
  6. bundle exec danger

ซึ่งก่อนที่ Danger จะทำงานได้นั้น ให้เราไปเพิ่ม Personal access tokens ใน GitHub โดยเข้าที่ Settings > Developer settings > Personal access tokens เพื่อจะนำ token ที่ได้ไปให้เจ้า Danger ใช้สำหรับการ Comment ใน GitHub นั่นเอง

personal access token สำหรับใช้ใน bitrise

นำ token ที่ได้มาใส่ใน Project > Workflow > Env Vars โดยใช้ key DANGER_GITHUB_API_TOKEN และ ติ๊ก Replace variables in inputs? ให้เป็นสีเขียว

เพิ่ม DANGER_GITHUB_API_TOKEN เข้าไปใน Env Vars

หลังจากนั้นให้ทำการตั้งค่า Triggers สำหรับการ Pull Request ใน GitHub เพื่อที่จะให้ CI ทำการรัน lint ทุกครั้งที่มีการขอ PR ใน GitHub และทำการ Review Code ให้โดยอัตโนมัติผ่าน Danger นั้นเอง โดยเข้าไปที่ Project > Workflow > Triggers > ADD TRIGGER โดยที่เราสามารถตั้งค่า source branch, target branch และ Workflow ที่จะทำให้ CI ทำงานได้ โดยในที่นี้จะตั้งค่าเป็น * เพื่อที่จะให้ CI ทำงานทุกครั้งที่มีการ PR นั่นเอง

Triggers

มาถึงตรงนี้ CI ก็พร้อมสำหรับการทำงานแล้วครับ เย้ !~

TESTING…

โดยการใส่ code เพิ่มเข้าไปสำหรับการ test โดยใส่

var username : String?

ในหน้า ViewController หน้าตาก็จะประมาณนี้

ใส่ code เพิ่มใน ViewController

โดยที่ถ้าผ่าน lint แล้ว lint จะทำการตรวจสอบ source code ตามเงื่อนไขที่ได้กำหนดไว้ในไฟล์ .swiftlint.yml โดยในที่นี้จะแก้เป็นดังนี้

var username : String? > var username: String?

จากนั้นทำการ push code ขึ้นใน branch ใหม่ โดยที่นี้จะใช้ชื่อ test/lint

branch test/lint

จากนั้นทำการขอ PR เข้าใน branch master แล้วเรามาดูการทำงานของ CI กันน~

create pull request

CI ทำงานน !

CI — In progress !!
bitrise ทำงาน !!
Passed !!
เสร็จแล้วจ้าา

RESULT…

เรามาดูผลการทำงานของ Swift-Lint และ Danger กันน !

swiftlint ทำงาน ! Danger แสดงผล !

จะเห็นได้ว่าคำสั่ง swiftlint ใน fastlane ทำการตรวจสอบ source code ที่มีการเปลี่ยนแปลงให้ ตามเงื่อนไขที่ได้กำหนดไว้ในไฟล์ .swiftlint.yml จากนั้นจะสร้าง Report ขึ้นมาเป็นไฟล์ที่ชื่อ swiftlint.result.xml และเจ้า Danger ก็จะนำ Report มาแสดงให้ใน Comment ของ GitHub นั่นเอง เอง เอง เอง ~

มาถึงตรงนี้ หากท่านใดทำการ PR (Pull Request) แล้ว bitrise ไม่ทำงานตาม Triggers ที่ได้กำหนดไว้ ให้ลองดูในส่วนของ Webhooks บน GitHub (Project > Settings > Webhooks) ว่าได้เปิดในส่วนของ PR (Pull Request) ไว้หรือไม่ ถ้าไม่ได้เปิดไว้ ก็สามารถกดเข้าไป Edit แล้วเลือกในส่วนของ Pull requests ได้เลยครับ

ด้านหลัง Webhooks ต้องมี pull_request and push นะจ๊ะ
ถ้าไม่มีก็มาเปิดเองได้เลย

เป็นยังไงกันบ้างครับ น่าจะเป็นบทความที่ยาวมากๆ ไม่รู้ว่าอ่านแล้วจะเข้าใจกันไหมนะ ฮ่าๆ

สรุป

จากที่ได้ลองใช้มาสักระยะหนึ่งก็พบว่า lint ช่วยลดภาระให้คนที่เป็น Reviewer ในตอน PR (Pull Request) ได้เยอะมาก แทนที่ Reviewer จะมานั่งตรวจสอบ code เองทุกครั้ง ก็ยกให้เป็นหน้าที่ของ lint จัดการไปเลย อีกทั้งยังทำให้ code ของเรามีรูปแบบการเขียนที่เป็นมาตรฐาน ตรวจสอบได้ง่าย ทั้งนี้ทั้งนั้น เราก็ควรแก้ไข code ตามที่ lint ได้แนะนำให้ใน Comment ด้วยนะ เพราะ ถ้าเกิดใช้ lint แล้ว แต่ไม่ได้แก้ไขตามที่ lint ได้แนะนำไป ก็ไม่มีประโยชน์อะไรที่จะใช้ครับ

แถมๆ

หากใครลง swiftlint ไว้ในเครื่อง ลงผ่าน (Command Line ด้วยคำสั่ง brew install swiftlint) สามารถใช้

swiftlint autocorrect [PATH]

ผ่าน Command Line ได้ด้วยนะ ซึ่งคำสั่งนี้จะทำหน้าที่ในการเปลี่ยน code ตามมาตรฐานของ lint ให้เองโดยอัตโนมัติ ! แต่ จะผ่านเงื่อนไขที่ได้กำหนดไว้ในไฟล์ .swiftlint.yml ตอน PR (Pull Request) หรือไม่ก็อีกเรื่องหนึ่งนะจ๊ะ

สามารถตรวจสอบเงื่อนไขในไฟล์ .swiftlint.yml ได้โดยใช้คำสั่ง

swiftlint lint

ผ่าน Command Line ใน Folder ที่ที่ .swiftlint.yml อยู่ได้ ก็จะเหมือนเป็นการรัน lint บนเครื่องของเรานั่นเอง จะได้รู้ว่าเราควรปรับเงื่อนไขตรงส่วนไหนให้เหมาะสมกับงานที่เราทำอยู่ครับ

สำหรับใครที่ยังไม่เข้าใจสามารถเข้าไปดูเพิ่มเติมได้ที่ ตัวอย่าง Project SwiftLintDanger

สุดท้ายนี้ หวังว่าบทความนี้คงจะมีประโยชน์ไม่มากก็น้อยนะครับ และ หากมีข้อผิดพลาดประการใด ก็ขออภัยมา ณ ที่นี้ด้วยนะครับผม

สุดท้ายของสุดท้าย ยังไงม้าก็แพ้ลา เพราะ ลาไปก่อน สวัสดีครับ

><”

--

--