Skip to content

Commit 1ec8bd2

Browse files
committed
Apply zizmor and update scripts
1 parent 89e51d5 commit 1ec8bd2

File tree

6 files changed

+116
-55
lines changed

6 files changed

+116
-55
lines changed

.github/.cspell/rust-dependencies.txt

Lines changed: 2 additions & 2 deletions
Some generated files are not rendered by default. Learn more about customizing how changed files appear on GitHub.

.github/workflows/ci.yml

Lines changed: 20 additions & 9 deletions
Original file line numberDiff line numberDiff line change
@@ -104,30 +104,41 @@ jobs:
104104
- name: Check action outputs
105105
run: |
106106
printf 'outputs.archive should not be empty\n'
107-
test -n "${{ steps.upload-rust-binary-action.outputs.archive }}"
107+
test -n "${OUTPUT_ARCHIVE}"
108108
109109
printf 'outputs.zip should be a file\n'
110-
test -f "${{ steps.upload-rust-binary-action.outputs.zip }}"
110+
test -f "${OUTPUT_ZIP}"
111111
112112
printf 'outputs.tar should be a file\n'
113-
test -f "${{ steps.upload-rust-binary-action.outputs.tar }}"
113+
test -f "${OUTPUT_TAR}"
114114
115115
printf 'outputs.tar-xz should be a file\n'
116-
test -f "${{ steps.upload-rust-binary-action.outputs.tar-xz }}"
116+
test -f "${OUTPUT_TAR_XZ}"
117117
118118
printf 'outputs.sha256 should be a file\n'
119-
test -f "${{ steps.upload-rust-binary-action.outputs.sha256 }}"
119+
test -f "${OUTPUT_SHA256}"
120120
121121
printf 'outputs.sha512 should be a file\n'
122-
test -f "${{ steps.upload-rust-binary-action.outputs.sha512 }}"
122+
test -f "${OUTPUT_SHA512}"
123123
124124
printf 'outputs.sha1 should be a file\n'
125-
test -f "${{ steps.upload-rust-binary-action.outputs.sha1 }}"
125+
test -f "${OUTPUT_SHA1}"
126126
127127
printf 'outputs.md5 should be a file\n'
128-
test -f "${{ steps.upload-rust-binary-action.outputs.md5 }}"
128+
test -f "${OUTPUT_MD5}"
129+
env:
130+
OUTPUT_ARCHIVE: ${{ steps.upload-rust-binary-action.outputs.archive }}
131+
OUTPUT_ZIP: ${{ steps.upload-rust-binary-action.outputs.zip }}
132+
OUTPUT_TAR: ${{ steps.upload-rust-binary-action.outputs.tar }}
133+
OUTPUT_TAR_XZ: ${{ steps.upload-rust-binary-action.outputs.tar-xz }}
134+
OUTPUT_SHA256: ${{ steps.upload-rust-binary-action.outputs.sha256 }}
135+
OUTPUT_SHA512: ${{ steps.upload-rust-binary-action.outputs.sha512 }}
136+
OUTPUT_SHA1: ${{ steps.upload-rust-binary-action.outputs.sha1 }}
137+
OUTPUT_MD5: ${{ steps.upload-rust-binary-action.outputs.md5 }}
129138
- name: Check b2 output
130139
if: ${{ contains(matrix.checksums || 'b2,sha256,sha512,sha1,md5', 'b2') }}
131140
run: |
132141
printf 'outputs.b2 should not be empty\n'
133-
test -n "${{ steps.upload-rust-binary-action.outputs.b2 }}"
142+
test -n "${OUTPUT_B2}"
143+
env:
144+
OUTPUT_B2: ${{ steps.upload-rust-binary-action.outputs.b2 }}

.github/zizmor.yml

Lines changed: 12 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,12 @@
1+
# zizmor configuration
2+
# https://docs.zizmor.sh/configuration/
3+
4+
rules:
5+
dependabot-cooldown: { disable: true } # Useless unless hash-pin is forced by unpinned-uses.
6+
ref-confusion: { disable: true } # TODO: Old GHA didn't work without this pattern in some cases, but does it seem to be fixed?
7+
secrets-inherit: { disable: true }
8+
unpinned-uses:
9+
config:
10+
policies:
11+
taiki-e/*: any
12+
'*': ref-pin

.shellcheckrc

Lines changed: 3 additions & 0 deletions
Original file line numberDiff line numberDiff line change
@@ -5,6 +5,9 @@
55
# https://github.com/koalaman/shellcheck/wiki/Optional
66
# https://google.github.io/styleguide/shellguide.html
77

8+
# https://github.com/koalaman/shellcheck/wiki/Directive#external-sources
9+
external-sources=true
10+
811
# https://github.com/koalaman/shellcheck/wiki/SC2249
912
# enable=add-default-case
1013

main.sh

Lines changed: 9 additions & 3 deletions
Original file line numberDiff line numberDiff line change
@@ -128,7 +128,9 @@ if [[ -n "${bin_name}" ]]; then
128128
# "cp: cannot stat 'app-*': No such file or directory".
129129
bail "glob pattern in 'bin' input option is not supported yet"
130130
fi
131-
while read -rd,; do bin_names+=("${REPLY}"); done <<<"${bin_name},"
131+
while read -rd,; do
132+
bin_names+=("${REPLY}")
133+
done <<<"${bin_name},"
132134
fi
133135
if [[ ${#bin_names[@]} -gt 1 ]] && [[ "${archive}" == *"\$bin"* ]]; then
134136
bail "when multiple binary names are specified, default archive name or '\$bin' variable cannot be used in 'archive' option"
@@ -145,7 +147,9 @@ if [[ -n "${include}" ]]; then
145147
# "cp: cannot stat 'LICENSE-*': No such file or directory".
146148
bail "glob pattern in 'include' input option is not supported yet"
147149
fi
148-
while read -rd,; do includes+=("${REPLY}"); done <<<"${include},"
150+
while read -rd,; do
151+
includes+=("${REPLY}")
152+
done <<<"${include},"
149153
fi
150154

151155
asset="${INPUT_ASSET:-}"
@@ -159,7 +163,9 @@ if [[ -n "${asset}" ]]; then
159163
# "cp: cannot stat 'LICENSE-*': No such file or directory".
160164
bail "glob pattern in 'asset' input option is not supported yet"
161165
fi
162-
while read -rd,; do assets+=("${REPLY}"); done <<<"${asset},"
166+
while read -rd,; do
167+
assets+=("${REPLY}")
168+
done <<<"${asset},"
163169
fi
164170

165171
checksum="${INPUT_CHECKSUM:-}"

tools/tidy.sh

Lines changed: 70 additions & 41 deletions
Original file line numberDiff line numberDiff line change
@@ -8,7 +8,7 @@ trap -- 'printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
88
cd -- "$(dirname -- "$0")"/..
99

1010
# USAGE:
11-
# ./tools/tidy.sh
11+
# GH_TOKEN=$(gh auth token) ./tools/tidy.sh
1212
#
1313
# Note: This script requires the following tools:
1414
# - git 1.8+
@@ -17,6 +17,7 @@ cd -- "$(dirname -- "$0")"/..
1717
# - python 3.6+ and pipx
1818
# - shfmt
1919
# - shellcheck
20+
# - zizmor
2021
# - cargo, rustfmt (if Rust code exists)
2122
# - clang-format (if C/C++/Protobuf code exists)
2223
# - parse-dockerfile <https://github.com/taiki-e/parse-dockerfile> (if Dockerfile exists)
@@ -117,11 +118,11 @@ check_alt() {
117118
check_hidden() {
118119
local res
119120
for file in "$@"; do
120-
check_alt ".${file}" "${file}" "$(comm -23 <(ls_files "*${file}") <(ls_files "*.${file}"))"
121+
check_alt ".${file}" "${file}" "$(LC_ALL=C comm -23 <(ls_files "*${file}") <(ls_files "*.${file}"))"
121122
done
122123
}
123124
sed_rhs_escape() {
124-
sed 's/\\/\\\\/g; s/\&/\\\&/g; s/\//\\\//g' <<<"$1"
125+
sed -E 's/\\/\\\\/g; s/\&/\\\&/g; s/\//\\\//g' <<<"$1"
125126
}
126127

127128
if [[ $# -gt 0 ]]; then
@@ -136,12 +137,8 @@ py_suffix=''
136137
if type -P python3 >/dev/null; then
137138
py_suffix=3
138139
fi
139-
yq() {
140-
pipx run yq "$@"
141-
}
142-
tomlq() {
143-
pipx run --spec yq tomlq "$@"
144-
}
140+
yq() { pipx run yq "$@"; }
141+
tomlq() { pipx run --spec yq tomlq "$@"; }
145142
case "$(uname -s)" in
146143
Linux)
147144
if [[ "$(uname -o)" == 'Android' ]]; then
@@ -166,10 +163,11 @@ case "$(uname -s)" in
166163
if [[ "${PATH}" != *'/usr/xpg4/bin'* ]]; then
167164
export PATH="/usr/xpg4/bin:${PATH}"
168165
fi
169-
# GNU/BSD grep/sed is required to run some checks, but most checks are okay with other POSIX grep/sed.
166+
# GNU/BSD sed is required.
167+
# GNU/BSD grep is required by some checks, but most checks are okay with other POSIX grep.
170168
# Solaris /usr/xpg4/bin/grep has -q, -E, -F, but no -o (non-POSIX).
171169
# Solaris /usr/xpg4/bin/sed has no -E (POSIX.1-2024) yet.
172-
for tool in sed grep; do
170+
for tool in 'grep' 'sed'; do
173171
if type -P "g${tool}" >/dev/null; then
174172
eval "${tool}() { g${tool} \"\$@\"; }"
175173
fi
@@ -188,12 +186,8 @@ case "$(uname -s)" in
188186
else
189187
jq() { command jq "$@" | tr -d '\r'; }
190188
fi
191-
yq() {
192-
pipx run yq "$@" | tr -d '\r'
193-
}
194-
tomlq() {
195-
pipx run --spec yq tomlq "$@" | tr -d '\r'
196-
}
189+
yq() { pipx run yq "$@" | tr -d '\r'; }
190+
tomlq() { pipx run --spec yq tomlq "$@" | tr -d '\r'; }
197191
fi
198192
fi
199193
;;
@@ -202,25 +196,34 @@ esac
202196

203197
check_install git
204198
exclude_from_ls_files=()
205-
# - `find` lists symlinks. `! ( -name <dir> -prune )` (.i.e., ignore <dir>) are manually listed from .gitignore.
206-
# - `git submodule status` lists submodules. Use sed to remove the first character indicates status ( |+|-).
199+
# - `find` lists symlinks. `! ( -name <dir> -prune )` means recursively ignore <dir>. `cut` removes the leading `./`.
200+
# This can be replaced with `fd -H -t l`.
201+
# - `git submodule status` lists submodules. The first `cut` removes the first character indicates status ( |+|-).
207202
# - `git ls-files --deleted` lists removed files.
208-
while IFS=$'\n' read -r line; do exclude_from_ls_files+=("${line}"); done < <({
209-
find . \! \( -name .git -prune \) \! \( -name target -prune \) \! \( -name tmp -prune \) -type l | cut -c3-
210-
git submodule status | sed 's/^.//' | cut -d' ' -f2
203+
find_prune=(\! \( -name .git -prune \))
204+
while IFS= read -r; do
205+
find_prune+=(\! \( -name "${REPLY}" -prune \))
206+
done < <(sed -E 's/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d' .gitignore)
207+
while IFS=$'\n' read -r; do
208+
exclude_from_ls_files+=("${REPLY}")
209+
done < <({
210+
find . "${find_prune[@]}" -type l | cut -c3-
211+
git submodule status | cut -c2- | cut -d' ' -f2
211212
git ls-files --deleted
212213
} | LC_ALL=C sort -u)
213214
exclude_from_ls_files_no_symlink=()
214-
while IFS=$'\n' read -r line; do exclude_from_ls_files_no_symlink+=("${line}"); done < <({
215-
git submodule status | sed 's/^.//' | cut -d' ' -f2
215+
while IFS=$'\n' read -r; do
216+
exclude_from_ls_files_no_symlink+=("${REPLY}")
217+
done < <({
218+
git submodule status | cut -c2- | cut -d' ' -f2
216219
git ls-files --deleted
217220
} | LC_ALL=C sort -u)
218221
ls_files() {
219222
if [[ "${1:-}" == '--include-symlink' ]]; then
220223
shift
221-
comm -23 <(git ls-files "$@" | LC_ALL=C sort) <(printf '%s\n' ${exclude_from_ls_files_no_symlink[@]+"${exclude_from_ls_files_no_symlink[@]}"})
224+
LC_ALL=C comm -23 <(git ls-files "$@" | LC_ALL=C sort) <(printf '%s\n' ${exclude_from_ls_files_no_symlink[@]+"${exclude_from_ls_files_no_symlink[@]}"})
222225
else
223-
comm -23 <(git ls-files "$@" | LC_ALL=C sort) <(printf '%s\n' ${exclude_from_ls_files[@]+"${exclude_from_ls_files[@]}"})
226+
LC_ALL=C comm -23 <(git ls-files "$@" | LC_ALL=C sort) <(printf '%s\n' ${exclude_from_ls_files[@]+"${exclude_from_ls_files[@]}"})
224227
fi
225228
}
226229

@@ -444,7 +447,7 @@ if [[ -n "$(ls_files '*.rs')" ]]; then
444447
new+="${line}"$'\a'
445448
done < <(tr '\n' '\a' <"${markdown}" | grep -Eo '<!-- tidy:sync-markdown-to-rustdoc:start[^ ]* -->.*<!-- tidy:sync-markdown-to-rustdoc:end -->')
446449
new+='<!-- tidy:sync-markdown-to-rustdoc:end -->'
447-
new=$(tr '\n' '\a' <"${lib}" | sed "s/<!-- tidy:sync-markdown-to-rustdoc:start[^ ]* -->.*<!-- tidy:sync-markdown-to-rustdoc:end -->/$(sed_rhs_escape "${new}")/" | tr '\a' '\n')
450+
new=$(tr '\n' '\a' <"${lib}" | sed -E "s/<!-- tidy:sync-markdown-to-rustdoc:start[^ ]* -->.*<!-- tidy:sync-markdown-to-rustdoc:end -->/$(sed_rhs_escape "${new}")/" | tr '\a' '\n')
448451
printf '%s\n' "${new}" >|"${lib}"
449452
check_diff "${lib}"
450453
done
@@ -676,7 +679,7 @@ elif check_install shellcheck; then
676679
# Others: false negative
677680
trap -- 'rm -- ./tools/.tidy-tmp; printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
678681
printf '%s\n' "${text}" >|./tools/.tidy-tmp
679-
if ! shellcheck --color="${color}" --exclude "${shellcheck_exclude}" ./tools/.tidy-tmp | sed "s/\.\/tools\/\.tidy-tmp/$(sed_rhs_escape "${display_path}")/g"; then
682+
if ! shellcheck --color="${color}" --exclude "${shellcheck_exclude}" ./tools/.tidy-tmp | sed -E "s/\.\/tools\/\.tidy-tmp/$(sed_rhs_escape "${display_path}")/g"; then
680683
error "check failed; please resolve the above shellcheck error(s)"
681684
fi
682685
rm -- ./tools/.tidy-tmp
@@ -821,7 +824,7 @@ EOF
821824
# Others: false negative
822825
trap -- 'rm -- ./tools/.tidy-tmp; printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
823826
printf '%s\n' "${text}" >|./tools/.tidy-tmp
824-
if ! shellcheck --color="${color}" --exclude "${shellcheck_exclude}" ./tools/.tidy-tmp | sed "s/\.\/tools\/\.tidy-tmp/$(sed_rhs_escape "${display_path}")/g"; then
827+
if ! shellcheck --color="${color}" --exclude "${shellcheck_exclude}" ./tools/.tidy-tmp | sed -E "s/\.\/tools\/\.tidy-tmp/$(sed_rhs_escape "${display_path}")/g"; then
825828
error "check failed; please resolve the above shellcheck error(s)"
826829
fi
827830
rm -- ./tools/.tidy-tmp
@@ -832,7 +835,8 @@ EOF
832835
# The top-level permissions must be weak as they are referenced by all jobs.
833836
permissions=$(jq -c '.permissions' <<<"${workflow}")
834837
case "${permissions}" in
835-
'{"contents":"read"}' | '{"contents":"none"}') ;;
838+
# `permissions: {}` means "all none": https://docs.github.com/en/actions/reference/workflows-and-actions/workflow-syntax#defining-access-for-the-github_token-scopes
839+
'{"contents":"read"}' | '{}') ;;
836840
null) error "${workflow_path}: top level permissions not found; it must be 'contents: read' or weaker permissions" ;;
837841
*) error "${workflow_path}: only 'contents: read' and weaker permissions are allowed at top level, but found '${permissions}'; if you want to use stronger permissions, please set job-level permissions" ;;
838842
esac
@@ -905,6 +909,20 @@ EOF
905909
fi
906910
fi
907911
fi
912+
zizmor_targets=(${workflows[@]+"${workflows[@]}"} ${actions[@]+"${actions[@]}"})
913+
if [[ -e .github/dependabot.yml ]]; then
914+
zizmor_targets+=(.github/dependabot.yml)
915+
fi
916+
if [[ ${#zizmor_targets[@]} -gt 0 ]]; then
917+
if [[ "${ostype}" =~ ^(netbsd|openbsd|dragonfly|illumos|solaris)$ ]] && [[ -n "${CI:-}" ]] && ! type -P zizmor >/dev/null; then
918+
warn "this check is skipped on NetBSD/OpenBSD/Dragonfly/illumos/Solaris due to installing zizmor is hard on these platform"
919+
elif check_install zizmor; then
920+
IFS=' '
921+
info "running \`zizmor -q ${zizmor_targets[*]}\`"
922+
IFS=$'\n\t'
923+
zizmor -q "${zizmor_targets[@]}"
924+
fi
925+
fi
908926
printf '\n'
909927
check_alt '.sh extension' '*.bash extension' "$(ls_files '*.bash')"
910928

@@ -913,7 +931,7 @@ check_alt '.sh extension' '*.bash extension' "$(ls_files '*.bash')"
913931
if [[ -f tools/.tidy-check-license-headers ]]; then
914932
info "checking license headers (experimental)"
915933
failed_files=''
916-
for p in $(comm -12 <(eval $(<tools/.tidy-check-license-headers) | LC_ALL=C sort) <(ls_files | LC_ALL=C sort)); do
934+
for p in $(LC_ALL=C comm -12 <(eval $(<tools/.tidy-check-license-headers) | LC_ALL=C sort) <(ls_files | LC_ALL=C sort)); do
917935
case "${p##*/}" in
918936
*.stderr | *.expanded.rs) continue ;; # generated files
919937
*.json) continue ;; # no comment support
@@ -981,11 +999,12 @@ if [[ -f .cspell.json ]]; then
981999
dependencies_words=$(npx -y cspell stdin --no-progress --no-summary --words-only --unique <<<"${dependencies}" || true)
9821000
fi
9831001
all_words=$(ls_files | { grep -Fv "${project_dictionary}" || true; } | npx -y cspell --file-list stdin --no-progress --no-summary --words-only --unique || true)
1002+
all_words+=$'\n'$(ls_files | npx -y cspell stdin --no-progress --no-summary --words-only --unique || true)
9841003
printf '%s\n' "${config_old}" >|.cspell.json
9851004
trap -- 'printf >&2 "%s\n" "${0##*/}: trapped SIGINT"; exit 1' SIGINT
9861005
cat >|.github/.cspell/rust-dependencies.txt <<EOF
987-
// This file is @generated by ${0##*/}.
988-
// It is not intended for manual editing.
1006+
# This file is @generated by ${0##*/}.
1007+
# It is not intended for manual editing.
9891008
EOF
9901009
if [[ -n "${dependencies_words}" ]]; then
9911010
LC_ALL=C sort -f >>.github/.cspell/rust-dependencies.txt <<<"${dependencies_words}"$'\n'
@@ -1000,11 +1019,20 @@ EOF
10001019
error "you may want to mark .github/.cspell/rust-dependencies.txt linguist-generated"
10011020
fi
10021021

1022+
# Check file names.
1023+
info "running \`git ls-files | npx -y cspell stdin --no-progress --no-summary --show-context\`"
1024+
if ! ls_files | npx -y cspell stdin --no-progress --no-summary --show-context; then
1025+
error "spellcheck failed: please fix uses of below words in file names or add to ${project_dictionary} if correct"
1026+
printf '=======================================\n'
1027+
{ ls_files | npx -y cspell stdin --no-progress --no-summary --words-only || true; } | sed -E "s/'s$//g" | LC_ALL=C sort -f -u
1028+
printf '=======================================\n\n'
1029+
fi
1030+
# Check file contains.
10031031
info "running \`git ls-files | npx -y cspell --file-list stdin --no-progress --no-summary\`"
10041032
if ! ls_files | npx -y cspell --file-list stdin --no-progress --no-summary; then
10051033
error "spellcheck failed: please fix uses of below words or add to ${project_dictionary} if correct"
10061034
printf '=======================================\n'
1007-
{ ls_files | npx -y cspell --file-list stdin --no-progress --no-summary --words-only || true; } | sed "s/'s$//g" | LC_ALL=C sort -f -u
1035+
{ ls_files | npx -y cspell --file-list stdin --no-progress --no-summary --words-only || true; } | sed -E "s/'s$//g" | LC_ALL=C sort -f -u
10081036
printf '=======================================\n\n'
10091037
fi
10101038

@@ -1015,8 +1043,8 @@ EOF
10151043
fi
10161044
case "${ostype}" in
10171045
# NetBSD uniq doesn't support -i flag.
1018-
netbsd) dup=$(sed '/^$/d; /^\/\//d' "${project_dictionary}" "${dictionary}" | LC_ALL=C sort -f | tr '[:upper:]' '[:lower:]' | LC_ALL=C uniq -d) ;;
1019-
*) dup=$(sed '/^$/d; /^\/\//d' "${project_dictionary}" "${dictionary}" | LC_ALL=C sort -f | LC_ALL=C uniq -d -i) ;;
1046+
netbsd) dup=$(sed -E 's/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d' "${project_dictionary}" "${dictionary}" | LC_ALL=C sort -f | tr '[:upper:]' '[:lower:]' | LC_ALL=C uniq -d) ;;
1047+
*) dup=$(sed -E 's/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d' "${project_dictionary}" "${dictionary}" | LC_ALL=C sort -f | LC_ALL=C uniq -d -i) ;;
10201048
esac
10211049
if [[ -n "${dup}" ]]; then
10221050
error "duplicated words in dictionaries; please remove the following words from ${project_dictionary}"
@@ -1027,13 +1055,14 @@ EOF
10271055
# Make sure the project-specific dictionary does not contain unused words.
10281056
if [[ -n "${REMOVE_UNUSED_WORDS:-}" ]]; then
10291057
grep_args=()
1030-
for word in $(grep -Ev '^//' "${project_dictionary}" || true); do
1058+
while IFS= read -r word; do
10311059
if ! grep -Eqi "^${word}$" <<<"${all_words}"; then
1032-
grep_args+=(-e "^${word}$")
1060+
grep_args+=(-e "^[ \t]*${word}[ \t]*(#.*|$)")
10331061
fi
1034-
done
1062+
done < <(sed -E 's/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d' "${project_dictionary}")
10351063
if [[ ${#grep_args[@]} -gt 0 ]]; then
10361064
info "removing unused words from ${project_dictionary}"
1065+
info "please commit changes made by the removal above"
10371066
res=$(grep -Ev "${grep_args[@]}" "${project_dictionary}" || true)
10381067
if [[ -n "${res}" ]]; then
10391068
printf '%s\n' "${res}" >|"${project_dictionary}"
@@ -1043,11 +1072,11 @@ EOF
10431072
fi
10441073
else
10451074
unused=''
1046-
for word in $(grep -Ev '^//' "${project_dictionary}" || true); do
1075+
while IFS= read -r word; do
10471076
if ! grep -Eqi "^${word}$" <<<"${all_words}"; then
10481077
unused+="${word}"$'\n'
10491078
fi
1050-
done
1079+
done < <(sed -E 's/#.*//g; s/^[ \t]+//g; s/\/[ \t]+$//g; /^$/d' "${project_dictionary}")
10511080
if [[ -n "${unused}" ]]; then
10521081
error "unused words in dictionaries; please remove the following words from ${project_dictionary} or run ${0##*/} locally"
10531082
print_fenced "${unused}"

0 commit comments

Comments
 (0)