From 17a04afe390d98fafa1c6f9a3baf73a5333fcb10 Mon Sep 17 00:00:00 2001 From: mirtlecn Date: Thu, 26 Mar 2026 17:20:57 +0800 Subject: [PATCH] test: add lint scripts. (#1512) MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit * ci: add lint scripts * fix(lint): handle luacheck summary with ansi colors * fix(ci): use luarocks to install luacheck * fix(ci): add sudo * test: 调整用例顺序 --- .github/workflows/release.yml | 11 +- .github/workflows/test.yml | 11 +- others/script/Makefile | 24 ++ others/script/lint/.luacheckrc | 37 +++ others/script/lint/.yamllint.yml | 20 ++ others/script/lint/README.md | 44 ++++ others/script/lint/run.sh | 230 ++++++++++++++++++ .../smoke/cases/rime_ice/input_cases.tsv | 42 +--- 8 files changed, 387 insertions(+), 32 deletions(-) create mode 100644 others/script/Makefile create mode 100644 others/script/lint/.luacheckrc create mode 100644 others/script/lint/.yamllint.yml create mode 100644 others/script/lint/README.md create mode 100644 others/script/lint/run.sh diff --git a/.github/workflows/release.yml b/.github/workflows/release.yml index 94a6ca1..5905260 100644 --- a/.github/workflows/release.yml +++ b/.github/workflows/release.yml @@ -26,17 +26,26 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Install lint dependencies + run: | + python3 -m pip install yamllint + sudo apt install -y luarocks + sudo luarocks install luacheck + - name: Cache rime cli bundle uses: actions/cache@v5 with: path: ${{ runner.temp }}/rime-cli-cache/rime-cli-linux-1.6.1.zip key: rime-cli-linux-1.6.1 + - name: Run lint + run: make -C others/script lint + - name: Run smoke test suite env: RIME_CLI_URL: https://github.com/mirtlecn/public/releases/download/v1.6.1/rime-cli-linux-1.6.1.zip RIME_CLI_CACHE_PATH: ${{ runner.temp }}/rime-cli-cache/rime-cli-linux-1.6.1.zip - run: bash ./others/script/smoke/run.sh rime_ice + run: make -C others/script smoke package: name: Package and release diff --git a/.github/workflows/test.yml b/.github/workflows/test.yml index 73b89f8..610ca64 100644 --- a/.github/workflows/test.yml +++ b/.github/workflows/test.yml @@ -25,14 +25,23 @@ jobs: - name: Checkout repository uses: actions/checkout@v6 + - name: Install lint dependencies + run: | + python3 -m pip install yamllint + sudo apt install -y luarocks + sudo luarocks install luacheck + - name: Cache rime cli bundle uses: actions/cache@v5 with: path: ${{ runner.temp }}/rime-cli-cache/rime-cli-linux-1.6.1.zip key: rime-cli-linux-1.6.1 + - name: Run lint + run: make -C others/script lint + - name: Run smoke test suite env: RIME_CLI_URL: https://github.com/mirtlecn/public/releases/download/v1.6.1/rime-cli-linux-1.6.1.zip RIME_CLI_CACHE_PATH: ${{ runner.temp }}/rime-cli-cache/rime-cli-linux-1.6.1.zip - run: bash ./others/script/smoke/run.sh rime_ice + run: make -C others/script smoke diff --git a/others/script/Makefile b/others/script/Makefile new file mode 100644 index 0000000..bd9d1bd --- /dev/null +++ b/others/script/Makefile @@ -0,0 +1,24 @@ +.DEFAULT_GOAL := default + +.PHONY: default build lint lint-yaml lint-lua lint-lua-format smoke + +default: + @: + +build: + go run . --rime_path "$$(cd ../.. && pwd)" + +lint: + bash lint/run.sh all + +lint-yaml: + bash lint/run.sh yaml-lint + +lint-lua: + bash lint/run.sh lua-lint + +lint-lua-format: + bash lint/run.sh lua-format-check + +smoke: + bash ./smoke/run.sh rime_ice diff --git a/others/script/lint/.luacheckrc b/others/script/lint/.luacheckrc new file mode 100644 index 0000000..a8daced --- /dev/null +++ b/others/script/lint/.luacheckrc @@ -0,0 +1,37 @@ +std = "lua54" + +globals = { + "Candidate", + "Component", + "ConfigValue", + "DictEntry", + "KeyEvent", + "KeySequence", + "LevelDb", + "Memory", + "Opencc", + "Projection", + "ReverseLookup", + "Schema", + "Segment", + "Set", + "ShadowCandidate", + "Spans", + "Switcher", + "TableDb", + "UniquifiedCandidate", + "UserDb", + "yield", + "log", + "rime_api", +} + +ignore = { + "111", + "121", + "142", + "143", + "211", + "212", + "631", +} diff --git a/others/script/lint/.yamllint.yml b/others/script/lint/.yamllint.yml new file mode 100644 index 0000000..92822ee --- /dev/null +++ b/others/script/lint/.yamllint.yml @@ -0,0 +1,20 @@ +extends: default + +rules: + braces: disable + brackets: disable + colons: disable + commas: disable + comments: disable + comments-indentation: disable + document-start: disable + empty-lines: + max: 2 + max-end: 1 + max-start: 0 + hyphens: disable + indentation: + indent-sequences: consistent + line-length: disable + new-line-at-end-of-file: disable + truthy: disable diff --git a/others/script/lint/README.md b/others/script/lint/README.md new file mode 100644 index 0000000..9e33715 --- /dev/null +++ b/others/script/lint/README.md @@ -0,0 +1,44 @@ +# YAML and Lua lint entrypoint + +This directory contains the lightweight lint entrypoint used by local commands. + +## Dependencies + +Current stage: + +- `yamllint` +- `luacheck` + +Example install on macOS: + +```bash +brew install yamllint luacheck +``` + +## Usage + +From the repository root: + +```bash +bash others/script/lint/run.sh yaml-lint +bash others/script/lint/run.sh lua-lint +bash others/script/lint/run.sh all +make -C others/script lint-yaml +make -C others/script lint-lua +make -C others/script lint +make -C others/script smoke +``` + +## Notes + +- The current implementation enables YAML linting and Lua linting. +- The first stage checks repository-root business YAML files and root + `*.schema.yaml` files. +- `*.schema.yaml` files are preprocessed before linting so the `pin_cand_filter` + tab-separated list does not break generic YAML parsing. +- `lua-lint` runs `luacheck` with a repository-local configuration derived from + the librime-lua globals currently used by this repository. +- `lua-format-check` is reserved for a later stage. +- `smoke` mirrors the current CI smoke invocation and runs + `bash ./others/script/smoke/run.sh rime_ice` through Make. +- GitHub Actions are intentionally not changed in this stage. diff --git a/others/script/lint/run.sh b/others/script/lint/run.sh new file mode 100644 index 0000000..59e6211 --- /dev/null +++ b/others/script/lint/run.sh @@ -0,0 +1,230 @@ +#!/usr/bin/env bash + +set -euo pipefail + +SCRIPT_DIR="$(cd "$(dirname "${BASH_SOURCE[0]}")" && pwd)" +REPO_ROOT="$(cd "${SCRIPT_DIR}/../../.." && pwd)" +YAMLLINT_CONFIG="${SCRIPT_DIR}/.yamllint.yml" +LUACHECK_CONFIG="${SCRIPT_DIR}/.luacheckrc" + +log() { + printf '[lint] %s\n' "$*" +} + +log_step() { + printf '[lint] 🔹 %s\n' "$*" +} + +log_pass() { + printf '[lint] ✅ %s\n' "$*" +} + +log_warn() { + printf '[lint] ⚠️ %s\n' "$*" +} + +log_fail() { + printf '[lint] 🔴 %s\n' "$*" >&2 +} + +find_business_yaml_files() { + ( + cd "${REPO_ROOT}" + find . \ + \( -path "./.git" -o -path "./.git/*" -o -path "./.github" -o -path "./.github/*" -o -path "./others" -o -path "./others/*" \) -prune \ + -o \ + \( -maxdepth 1 -type f \( -name "*.yaml" -o -name "*.yml" \) ! -name "*.dict.yaml" -print \) + ) | sort +} + +sanitize_schema_yaml() { + local source_file="$1" + local output_file="$2" + + awk ' + function indent_width(line) { + match(line, /^ */) + return RLENGTH + } + + BEGIN { + in_pin_cand_filter = 0 + pin_indent = -1 + } + + { + if (!in_pin_cand_filter) { + if ($0 ~ /^ *pin_cand_filter:[[:space:]]*$/) { + in_pin_cand_filter = 1 + pin_indent = indent_width($0) + } + print + next + } + + if ($0 ~ /^[[:space:]]*$/ || $0 ~ /^[[:space:]]*#/) { + print + next + } + + current_indent = indent_width($0) + if (current_indent <= pin_indent) { + in_pin_cand_filter = 0 + print + next + } + + if ($0 ~ /^[[:space:]]*-[[:space:]]/) { + print substr($0, 1, current_indent) "- __PIN_CAND_FILTER_PLACEHOLDER__" + next + } + + print + } + ' "${source_file}" > "${output_file}" +} + +require_tool() { + local tool_name="$1" + if ! command -v "${tool_name}" >/dev/null 2>&1; then + log_fail "missing required tool: ${tool_name}" + return 1 + fi +} + +run_yaml_lint() { + require_tool yamllint + + local yaml_files=() + local temp_dir='' + local lint_status=0 + local total_start_time + total_start_time="$(date +%s)" + while IFS= read -r yaml_file; do + yaml_files+=("${yaml_file}") + done < <(find_business_yaml_files) + + if [[ "${#yaml_files[@]}" -eq 0 ]]; then + log_warn 'no business YAML files found' + return 0 + fi + + temp_dir="$(mktemp -d)" + trap 'if [[ -n "${temp_dir:-}" ]]; then rm -rf "${temp_dir}"; fi' RETURN + + log_step "yaml-lint checking ${#yaml_files[@]} file(s)" + + for yaml_file in "${yaml_files[@]}"; do + local lint_target="${yaml_file}" + local lint_output='' + local file_start_time + local file_elapsed + file_start_time="$(date +%s)" + + log_step "yaml-lint checking ${yaml_file}" + + if [[ "${yaml_file}" == *.schema.yaml ]]; then + lint_target="${temp_dir}/$(basename "${yaml_file}")" + sanitize_schema_yaml "${REPO_ROOT}/${yaml_file#./}" "${lint_target}" + fi + + if ! lint_output="$(cd "${REPO_ROOT}" && yamllint -f parsable -c "${YAMLLINT_CONFIG}" "${lint_target}" 2>&1)"; then + lint_status=1 + if [[ "${lint_target}" != "${yaml_file}" ]]; then + lint_output="${lint_output//${lint_target}/${yaml_file}}" + fi + printf '%s\n' "${lint_output}" + log_fail "yaml-lint failed for ${yaml_file}" + fi + + file_elapsed="$(( $(date +%s) - file_start_time ))" + log_pass "yaml-lint finished ${yaml_file} in ${file_elapsed}s" + done + + log_pass "yaml-lint completed in $(( $(date +%s) - total_start_time ))s" + + return "${lint_status}" +} + +run_lua_lint() { + require_tool luacheck + local lint_output='' + local lint_status=0 + local last_line='' + local normalized_last_line='' + log_step 'lua-lint checking lua/**/*.lua' + + set +e + lint_output="$( + ( + cd "${REPO_ROOT}" + luacheck lua --no-default-config --config "${LUACHECK_CONFIG}" --codes --ranges + ) 2>&1 + )" + lint_status=$? + set -e + + printf '%s\n' "${lint_output}" + last_line="$(printf '%s\n' "${lint_output}" | tail -n 1)" + normalized_last_line="$(printf '%s\n' "${last_line}" | sed -E $'s/\x1B\\[[0-9;]*[[:alpha:]]//g')" + if [[ "${lint_status}" -eq 0 ]]; then + log_pass 'lua-lint passed' + return 0 + fi + + if printf '%s\n' "${normalized_last_line}" | grep -Eq '0 errors?'; then + log_warn 'lua-lint completed with warnings' + return 0 + fi + log_fail 'lua-lint completed with errors' + return 1 +} + +run_lua_format_check() { + log_warn 'lua-format-check is not enabled yet' + return 1 +} + +run_all() { + run_yaml_lint + run_lua_lint +} + +usage() { + cat <<'EOF' +Usage: bash others/script/lint/run.sh + +Commands: + yaml-lint + lua-lint + lua-format-check + all +EOF +} + +main() { + local command="${1:-}" + case "${command}" in + yaml-lint) + run_yaml_lint + ;; + lua-lint) + run_lua_lint + ;; + lua-format-check) + run_lua_format_check + ;; + all) + run_all + ;; + help|-h|--help) + usage + ;; + *) + usage >&2 + return 1 + ;; + esac +} + +main "$@" diff --git a/others/script/smoke/cases/rime_ice/input_cases.tsv b/others/script/smoke/cases/rime_ice/input_cases.tsv index 1daee69..638e322 100644 --- a/others/script/smoke/cases/rime_ice/input_cases.tsv +++ b/others/script/smoke/cases/rime_ice/input_cases.tsv @@ -10,9 +10,6 @@ 基础:Emoji输入 rime_ice nihao{2} 👋 基础:uU拆字反查 rime_ice uUrenrenren{space} 众 基础:v键符号输入 rime_ice v1{space} 一 - -# ------ 插件 ------ -# case_id schema_id key_sequence expected_text 插件:英文全大写 rime_ice HELLO{space} HELLO 插件:英文首字母大写 rime_ice Hello{space} Hello 插件:置顶候选 rime_ice d{space} 的 @@ -33,16 +30,14 @@ # ------------------ # 方案:自然码双拼 # ------ 基础 ------ +# 基础:自定义短语 double_pinyin ig{space} 一个 基础:中文输入 double_pinyin wusspnyn{space} 雾凇拼音 基础:英文输入 double_pinyin hello{space} hello 基础:英文派生 double_pinyin PtwoP{space} P2P 基础:中英混输 double_pinyin Xgd{space} X光 -# 基础:自定义短语 double_pinyin ig{space} 一个 基础:Emoji输入 double_pinyin nihk{2} 👋 基础:uU拆字反查 double_pinyin uUrenrenren{space} 众 基础:v键符号输入 double_pinyin V1{space} 一 - -# ------ 插件 ------ 插件:英文全大写 double_pinyin HELLO{space} HELLO 插件:英文首字母大写 double_pinyin Hello{space} Hello 插件:置顶候选 double_pinyin d{space} 的 @@ -63,24 +58,22 @@ # ------------------ # 方案:智能 ABC 双拼 # ------ 基础 ------ +# 基础:自定义短语 double_pinyin_abc ig{space} 一个 +# 插件:英文降权 double_pinyin_abc bail{space} 不确定 +# 插件:英文降权 double_pinyin_abc bail{2} 不确定 基础:中文输入 double_pinyin_abc wusspcyc{space} 雾凇拼音 基础:英文输入 double_pinyin_abc hello{space} hello 基础:英文派生 double_pinyin_abc PtwoP{space} P2P 基础:中英混输 double_pinyin_abc Xgt{space} X光 -# 基础:自定义短语 double_pinyin_abc ig{space} 一个 基础:Emoji输入 double_pinyin_abc nihk{2} 👋 基础:uU拆字反查 double_pinyin_abc uUrenrenren{space} 众 基础:v键符号输入 double_pinyin_abc V1{space} 一 - -# ------ 插件 ------ 插件:英文全大写 double_pinyin_abc HELLO{space} HELLO 插件:英文首字母大写 double_pinyin_abc Hello{space} Hello 插件:置顶候选 double_pinyin_abc d{space} 的 插件:Unicode输入 double_pinyin_abc U4f60{space} 你 插件:日期输入 double_pinyin_abc date{space} @today:%Y-%m-%d 插件:UUID输入 double_pinyin_abc uuid{space} @regex:^commit: [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ -# 插件:英文降权 double_pinyin_abc bail{space} 不确定 -# 插件:英文降权 double_pinyin_abc bail{2} 不确定 插件:数字输入 double_pinyin_abc R123{space} 一百二十三 插件:计算器 double_pinyin_abc cC1+2{space} 3 插件:农历输入:农历 double_pinyin_abc N19700101{space} 一九六九年冬月廿四 @@ -93,16 +86,14 @@ # ------------------ # 方案:微软双拼 # ------ 基础 ------ +# 基础:自定义短语 double_pinyin_mspy ig{space} 一个 基础:中文输入 double_pinyin_mspy wusspnyn{space} 雾凇拼音 基础:英文输入 double_pinyin_mspy hello{space} hello 基础:英文派生 double_pinyin_mspy PtwoP{space} P2P 基础:中英混输 double_pinyin_mspy Xgd{space} X光 -# 基础:自定义短语 double_pinyin_mspy ig{space} 一个 基础:Emoji输入 double_pinyin_mspy nihk{2} 👋 基础:uU拆字反查 double_pinyin_mspy uUrenrenren{space} 众 基础:v键符号输入 double_pinyin_mspy V1{space} 一 - -# ------ 插件 ------ 插件:英文全大写 double_pinyin_mspy HELLO{space} HELLO 插件:英文首字母大写 double_pinyin_mspy Hello{space} Hello 插件:置顶候选 double_pinyin_mspy d{space} 的 @@ -123,16 +114,14 @@ # ------------------ # 方案:搜狗双拼 # ------ 基础 ------ +# 基础:自定义短语 double_pinyin_sogou ig{space} 一个 基础:中文输入 double_pinyin_sogou wusspnyn{space} 雾凇拼音 基础:英文输入 double_pinyin_sogou hello{space} hello 基础:英文派生 double_pinyin_sogou PtwoP{space} P2P 基础:中英混输 double_pinyin_sogou Xgd{space} X光 -# 基础:自定义短语 double_pinyin_sogou ig{space} 一个 基础:Emoji输入 double_pinyin_sogou nihk{2} 👋 基础:uU拆字反查 double_pinyin_sogou uUrenrenren{space} 众 基础:v键符号输入 double_pinyin_sogou V1{space} 一 - -# ------ 插件 ------ 插件:英文全大写 double_pinyin_sogou HELLO{space} HELLO 插件:英文首字母大写 double_pinyin_sogou Hello{space} Hello 插件:置顶候选 double_pinyin_sogou d{space} 的 @@ -153,16 +142,14 @@ # ------------------ # 方案:紫光双拼 # ------ 基础 ------ +# 基础:自定义短语 double_pinyin_ziguang ig{space} 一个 基础:中文输入 double_pinyin_ziguang wushpyyy{space} 雾凇拼音 基础:英文输入 double_pinyin_ziguang hello{space} hello 基础:英文派生 double_pinyin_ziguang PtwoP{space} P2P 基础:中英混输 double_pinyin_ziguang Xgg{space} X光 -# 基础:自定义短语 double_pinyin_ziguang ig{space} 一个 基础:Emoji输入 double_pinyin_ziguang nihq{2} 👋 基础:uU拆字反查 double_pinyin_ziguang uUrenrenren{space} 众 基础:v键符号输入 double_pinyin_ziguang V1{space} 一 - -# ------ 插件 ------ 插件:英文全大写 double_pinyin_ziguang HELLO{space} HELLO 插件:英文首字母大写 double_pinyin_ziguang Hello{space} Hello 插件:置顶候选 double_pinyin_ziguang d{space} 的 @@ -183,24 +170,22 @@ # ------------------ # 方案:拼音加加双拼 # ------ 基础 ------ +# 基础:自定义短语 double_pinyin_jiajia ig{space} 一个 +# 插件:英文降权 double_pinyin_jiajia bail{space} 不确定 +# 插件:英文降权 double_pinyin_jiajia bail{2} 不确定 基础:中文输入 double_pinyin_jiajia wusyplyl{space} 雾凇拼音 基础:英文输入 double_pinyin_jiajia hello{space} hello 基础:英文派生 double_pinyin_jiajia PtwoP{space} P2P 基础:中英混输 double_pinyin_jiajia Xgh{space} X光 -# 基础:自定义短语 double_pinyin_jiajia ig{space} 一个 基础:Emoji输入 double_pinyin_jiajia nihd{2} 👋 基础:uU拆字反查 double_pinyin_jiajia uUrenrenren{space} 众 基础:v键符号输入 double_pinyin_jiajia V1{space} 一 - -# ------ 插件 ------ 插件:英文全大写 double_pinyin_jiajia HELLO{space} HELLO 插件:英文首字母大写 double_pinyin_jiajia Hello{space} Hello 插件:置顶候选 double_pinyin_jiajia d{space} 的 插件:Unicode输入 double_pinyin_jiajia U4f60{space} 你 插件:日期输入 double_pinyin_jiajia date{space} @today:%Y-%m-%d 插件:UUID输入 double_pinyin_jiajia uuid{space} @regex:^commit: [0-9a-f]{8}-[0-9a-f]{4}-4[0-9a-f]{3}-[89ab][0-9a-f]{3}-[0-9a-f]{12}$ -# 插件:英文降权 double_pinyin_jiajia bail{space} 不确定 -# 插件:英文降权 double_pinyin_jiajia bail{2} 不确定 插件:数字输入 double_pinyin_jiajia R123{space} 一百二十三 插件:计算器 double_pinyin_jiajia cC1+2{space} 3 插件:农历输入:农历 double_pinyin_jiajia N19700101{space} 一九六九年冬月廿四 @@ -213,17 +198,14 @@ # ------------------ # 方案:小鹤双拼 # ------ 基础 ------ +# 基础:自定义短语 double_pinyin_flypy ig{space} 一个 +基础:Emoji输入 double_pinyin_flypy nihc{2} 👋 基础:中文输入 double_pinyin_flypy wusspbyb{space} 雾凇拼音 基础:英文输入 double_pinyin_flypy hello{space} hello 基础:英文派生 double_pinyin_flypy PtwoP{space} P2P 基础:中英混输 double_pinyin_flypy Xgl{space} X光 -# 基础:自定义短语 double_pinyin_flypy ig{space} 一个 -基础:Emoji输入 double_pinyin_flypy nihc{2} 👋 基础:uU拆字反查 double_pinyin_flypy uUrenrenren{space} 众 基础:v键符号输入 double_pinyin_flypy V1{space} 一 - -# ------ 插件 ------ -# case_id schema_id key_sequence expected_text 插件:英文全大写 double_pinyin_flypy HELLO{space} HELLO 插件:英文首字母大写 double_pinyin_flypy Hello{space} Hello 插件:置顶候选 double_pinyin_flypy d{space} 的