Skip to content

Commit 7b8d373

Browse files
aduh95targos
authored andcommitted
tools: lint shell scripts
PR-URL: #36099 Reviewed-By: Rich Trott <rtrott@gmail.com>
1 parent eb29a16 commit 7b8d373

12 files changed

+255
-54
lines changed

.github/workflows/linters.yml

+7
Original file line numberDiff line numberDiff line change
@@ -77,6 +77,13 @@ jobs:
7777
run: |
7878
make lint-py-build || true
7979
NODE=$(command -v node) make lint-py
80+
lint-sh:
81+
runs-on: ubuntu-20.04
82+
steps:
83+
- uses: actions/checkout@v2
84+
- run: shellcheck -V
85+
- name: Lint Shell scripts
86+
run: tools/lint-sh.js .
8087

8188
lint-codeowners:
8289
runs-on: ubuntu-latest

tools/actions/commit-queue.sh

+6-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/bin/sh
22

33
set -xe
44

@@ -14,19 +14,19 @@ API_URL=https://api.github.com
1414
COMMIT_QUEUE_LABEL='commit-queue'
1515
COMMIT_QUEUE_FAILED_LABEL='commit-queue-failed'
1616

17-
function issueUrl() {
17+
issueUrl() {
1818
echo "$API_URL/repos/${OWNER}/${REPOSITORY}/issues/${1}"
1919
}
2020

21-
function labelsUrl() {
21+
labelsUrl() {
2222
echo "$(issueUrl "${1}")/labels"
2323
}
2424

25-
function commentsUrl() {
25+
commentsUrl() {
2626
echo "$(issueUrl "${1}")/comments"
2727
}
2828

29-
function gitHubCurl() {
29+
gitHubCurl() {
3030
url=$1
3131
method=$2
3232
shift 2
@@ -67,6 +67,7 @@ for pr in "$@"; do
6767
if ! tail -n 10 output | grep '. Post "Landed in .*/pull/'"${pr}"; then
6868
gitHubCurl "$(labelsUrl "$pr")" POST --data '{"labels": ["'"${COMMIT_QUEUE_FAILED_LABEL}"'"]}'
6969

70+
# shellcheck disable=SC2154
7071
cqurl="${GITHUB_SERVER_URL}/${OWNER}/${REPOSITORY}/actions/runs/${GITHUB_RUN_ID}"
7172
jq -n --arg content "<details><summary>Commit Queue failed</summary><pre>$(cat output)</pre><a href='$cqurl'>$cqurl</a></details>" '{body: $content}' > output.json
7273
cat output.json

tools/actions/start-ci.sh

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/bin/sh
22

33
set -xe
44

@@ -10,15 +10,15 @@ REQUEST_CI_LABEL='request-ci'
1010
REQUEST_CI_FAILED_LABEL='request-ci-failed'
1111
shift 3
1212

13-
function issueUrl() {
13+
issueUrl() {
1414
echo "$API_URL/repos/${OWNER}/${REPOSITORY}/issues/${1}"
1515
}
1616

17-
function labelsUrl() {
17+
labelsUrl() {
1818
echo "$(issueUrl "${1}")/labels"
1919
}
2020

21-
function commentsUrl() {
21+
commentsUrl() {
2222
echo "$(issueUrl "${1}")/comments"
2323
}
2424

@@ -33,7 +33,7 @@ for pr in "$@"; do
3333
ncu-ci run "$pr" >output 2>&1 || ci_started=no
3434
cat output
3535

36-
if [ "$ci_started" == "no" ]; then
36+
if [ "$ci_started" = "no" ]; then
3737
# Do we need to reset?
3838
curl -sL --request PUT \
3939
--url "$(labelsUrl "$pr")" \

tools/create_expfile.sh

+4-4
Original file line numberDiff line numberDiff line change
@@ -37,15 +37,15 @@
3737
echo "Searching $1 to write out expfile to $2"
3838

3939
# This special sequence must be at the start of the exp file.
40-
echo "#!." > $2.tmp
40+
echo "#!." > "$2.tmp"
4141

4242
# Pull the symbols from the .a files.
43-
find $1 -name "*.a" | grep -v gtest \
43+
find "$1" -name "*.a" | grep -v gtest \
4444
| xargs nm -Xany -BCpg \
4545
| awk '{
4646
if ((($2 == "T") || ($2 == "D") || ($2 == "B")) &&
4747
(substr($3,1,1) != ".")) { print $3 }
4848
}' \
49-
| sort -u >> $2.tmp
49+
| sort -u >> "$2.tmp"
5050

51-
mv -f $2.tmp $2
51+
mv -f "$2.tmp" "$2"

tools/lint-pr-commit-message.sh

+5-5
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/usr/bin/env bash
1+
#!/bin/sh
22

33
# Shell script to lint the message of the first commit in a pull request.
44
#
@@ -13,7 +13,7 @@ PR_ID=$1;
1313
if [ -z "${PR_ID}" ]; then
1414
# Attempt to work out the PR number based on current HEAD
1515
if HEAD_COMMIT="$( git rev-parse HEAD )"; then
16-
if SEARCH_RESULTS="$( curl -s ${GH_API_URL}/search/issues?q=sha:${HEAD_COMMIT}+type:pr+repo:nodejs/node )"; then
16+
if SEARCH_RESULTS="$( curl -s "${GH_API_URL}/search/issues?q=sha:${HEAD_COMMIT}+type:pr+repo:nodejs/node" )"; then
1717
if FOUND_PR="$( node -p 'JSON.parse(process.argv[1]).items[0].number' "${SEARCH_RESULTS}" 2> /dev/null )"; then
1818
PR_ID=${FOUND_PR}
1919
fi
@@ -26,13 +26,13 @@ if [ -z "${PR_ID}" ]; then
2626
exit 1
2727
fi
2828

29-
PATCH=$( curl -sL https://github.com/nodejs/node/pull/${PR_ID}.patch | grep '^From ' )
29+
PATCH=$( curl -sL "https://github.com/nodejs/node/pull/${PR_ID}.patch" | grep '^From ' )
3030
if FIRST_COMMIT="$( echo "$PATCH" | awk '/^From [0-9a-f]{40} / { if (count++ == 0) print $2 }' )"; then
31-
MESSAGE=$( git show --quiet --format='format:%B' $FIRST_COMMIT )
31+
MESSAGE=$( git show --quiet --format='format:%B' "$FIRST_COMMIT" )
3232
echo "
3333
*** Linting the first commit message for pull request ${PR_ID}
3434
*** according to the guidelines at https://goo.gl/p2fr5Q.
35-
*** Commit message for $(echo $FIRST_COMMIT | cut -c 1-10) is:
35+
*** Commit message for $(echo "$FIRST_COMMIT" | cut -c 1-10) is:
3636
${MESSAGE}
3737
"
3838
npx -q core-validate-commit --no-validate-metadata "${FIRST_COMMIT}"

tools/lint-sh.js

+185
Original file line numberDiff line numberDiff line change
@@ -0,0 +1,185 @@
1+
#!/usr/bin/env node
2+
'use strict';
3+
4+
const { execSync, spawn } = require('child_process');
5+
const { promises: fs, readdirSync, statSync } = require('fs');
6+
const { extname, join, relative, resolve } = require('path');
7+
8+
const FIX_MODE_ENABLED = process.argv.includes('--fix');
9+
const USE_NPX = process.argv.includes('--from-npx');
10+
11+
const SHELLCHECK_EXE_NAME = 'shellcheck';
12+
const SHELLCHECK_OPTIONS = ['--shell=sh', '--severity=info', '--enable=all'];
13+
if (FIX_MODE_ENABLED) SHELLCHECK_OPTIONS.push('--format=diff');
14+
else if (process.env.GITHUB_ACTIONS) SHELLCHECK_OPTIONS.push('--format=json');
15+
16+
const SPAWN_OPTIONS = {
17+
cwd: null,
18+
shell: false,
19+
stdio: ['pipe', 'pipe', 'inherit'],
20+
};
21+
22+
function* findScriptFilesRecursively(dirPath) {
23+
const entries = readdirSync(dirPath, { withFileTypes: true });
24+
25+
for (const entry of entries) {
26+
const path = join(dirPath, entry.name);
27+
28+
if (
29+
entry.isDirectory() &&
30+
entry.name !== 'build' &&
31+
entry.name !== 'changelogs' &&
32+
entry.name !== 'deps' &&
33+
entry.name !== 'fixtures' &&
34+
entry.name !== 'gyp' &&
35+
entry.name !== 'inspector_protocol' &&
36+
entry.name !== 'node_modules' &&
37+
entry.name !== 'out' &&
38+
entry.name !== 'tmp'
39+
) {
40+
yield* findScriptFilesRecursively(path);
41+
} else if (entry.isFile() && extname(entry.name) === '.sh') {
42+
yield path;
43+
}
44+
}
45+
}
46+
47+
const expectedHashBang = Buffer.from('#!/bin/sh\n');
48+
async function hasInvalidHashBang(fd) {
49+
const { length } = expectedHashBang;
50+
51+
const actual = Buffer.allocUnsafe(length);
52+
await fd.read(actual, 0, length, 0);
53+
54+
return Buffer.compare(actual, expectedHashBang);
55+
}
56+
57+
async function checkFiles(...files) {
58+
const flags = FIX_MODE_ENABLED ? 'r+' : 'r';
59+
await Promise.all(
60+
files.map(async (file) => {
61+
const fd = await fs.open(file, flags);
62+
if (await hasInvalidHashBang(fd)) {
63+
if (FIX_MODE_ENABLED) {
64+
const file = await fd.readFile();
65+
66+
const fileContent =
67+
file[0] === '#'.charCodeAt() ?
68+
file.subarray(file.indexOf('\n') + 1) :
69+
file;
70+
71+
const toWrite = Buffer.concat([expectedHashBang, fileContent]);
72+
await fd.truncate(toWrite.length);
73+
await fd.write(toWrite, 0, toWrite.length, 0);
74+
} else {
75+
if (!process.exitCode) process.exitCode = 1;
76+
console.error(
77+
(process.env.GITHUB_ACTIONS ?
78+
`::error file=${file},line=1,col=1::` :
79+
'Fixable with --fix: ') +
80+
`Invalid hashbang for ${file} (expected /bin/sh).`
81+
);
82+
}
83+
}
84+
await fd.close();
85+
})
86+
);
87+
88+
const stdout = await new Promise((resolve, reject) => {
89+
const SHELLCHECK_EXE =
90+
process.env.SHELLCHECK ||
91+
execSync('command -v ' + (USE_NPX ? 'npx' : SHELLCHECK_EXE_NAME))
92+
.toString()
93+
.trim();
94+
const NPX_OPTIONS = USE_NPX ? [SHELLCHECK_EXE_NAME] : [];
95+
96+
const shellcheck = spawn(
97+
SHELLCHECK_EXE,
98+
[
99+
...NPX_OPTIONS,
100+
...SHELLCHECK_OPTIONS,
101+
...(FIX_MODE_ENABLED ?
102+
files.map((filePath) => relative(SPAWN_OPTIONS.cwd, filePath)) :
103+
files),
104+
],
105+
SPAWN_OPTIONS
106+
);
107+
shellcheck.once('error', reject);
108+
109+
let json = '';
110+
let childProcess = shellcheck;
111+
if (FIX_MODE_ENABLED) {
112+
const GIT_EXE =
113+
process.env.GIT || execSync('command -v git').toString().trim();
114+
115+
const gitApply = spawn(GIT_EXE, ['apply'], SPAWN_OPTIONS);
116+
shellcheck.stdout.pipe(gitApply.stdin);
117+
shellcheck.once('exit', (code) => {
118+
if (!process.exitCode && code) process.exitCode = code;
119+
});
120+
gitApply.stdout.pipe(process.stdout);
121+
122+
gitApply.once('error', reject);
123+
childProcess = gitApply;
124+
} else if (process.env.GITHUB_ACTIONS) {
125+
shellcheck.stdout.on('data', (chunk) => {
126+
json += chunk;
127+
});
128+
} else {
129+
shellcheck.stdout.pipe(process.stdout);
130+
}
131+
childProcess.once('exit', (code) => {
132+
if (!process.exitCode && code) process.exitCode = code;
133+
resolve(json);
134+
});
135+
});
136+
137+
if (!FIX_MODE_ENABLED && process.env.GITHUB_ACTIONS) {
138+
const data = JSON.parse(stdout);
139+
for (const { file, line, column, message } of data) {
140+
console.error(
141+
`::error file=${file},line=${line},col=${column}::` +
142+
`${file}:${line}:${column}: ${message}`
143+
);
144+
}
145+
}
146+
}
147+
148+
const USAGE_STR =
149+
`Usage: ${process.argv[1]} <path> [--fix] [--from-npx]\n` +
150+
'\n' +
151+
'Environment variables:\n' +
152+
' - SHELLCHECK: absolute path to `shellcheck`. If not provided, the\n' +
153+
' script will use the result of `command -v shellcheck`, or\n' +
154+
' `$(command -v npx) shellcheck` if the flag `--from-npx` is provided\n' +
155+
' (may require an internet connection).\n' +
156+
' - GIT: absolute path to `git`. If not provided, the \n' +
157+
' script will use the result of `command -v git`.\n';
158+
159+
if (
160+
process.argv.length < 3 ||
161+
process.argv.includes('-h') ||
162+
process.argv.includes('--help')
163+
) {
164+
console.log(USAGE_STR);
165+
} else {
166+
console.log('Running Shell scripts checker...');
167+
const entryPoint = resolve(process.argv[2]);
168+
const stats = statSync(entryPoint, { throwIfNoEntry: false });
169+
170+
function onError(e) {
171+
console.log(USAGE_STR);
172+
console.error(e);
173+
process.exitCode = 1;
174+
}
175+
if (stats?.isDirectory()) {
176+
SPAWN_OPTIONS.cwd = entryPoint;
177+
checkFiles(...findScriptFilesRecursively(entryPoint)).catch(onError);
178+
} else if (stats?.isFile()) {
179+
SPAWN_OPTIONS.cwd = process.cwd();
180+
checkFiles(entryPoint).catch(onError);
181+
} else {
182+
onError(new Error('You must provide a valid directory or file path. ' +
183+
`Received '${process.argv[2]}'.`));
184+
}
185+
}

tools/macos-firewall.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,4 +1,4 @@
1-
#!/bin/bash
1+
#!/bin/sh
22
# Script that adds rules to Mac OS X Socket Firewall to avoid
33
# popups asking to accept incoming network connections when
44
# running tests.

tools/make-v8.sh

+17-13
Original file line numberDiff line numberDiff line change
@@ -1,37 +1,41 @@
1-
#!/bin/bash -xe
1+
#!/bin/sh
2+
3+
set -xe
24

35
BUILD_ARCH_TYPE=$1
46
V8_BUILD_OPTIONS=$2
57

6-
cd deps/v8
7-
find . -type d -name .git | xargs rm -rf
8+
cd deps/v8 || exit
9+
find . -type d -name .git -print0 | xargs -0 rm -rf
810
tools/node/fetch_deps.py .
911

1012
ARCH="`arch`"
11-
if [[ "$ARCH" == "s390x" ]] || [[ "$ARCH" == "ppc64le" ]]; then
13+
if [ "$ARCH" = "s390x" ] || [ "$ARCH" = "ppc64le" ]; then
1214
TARGET_ARCH=$ARCH
13-
if [[ "$ARCH" == "ppc64le" ]]; then
15+
if [ "$ARCH" = "ppc64le" ]; then
1416
TARGET_ARCH="ppc64"
1517
fi
1618
# set paths manually for now to use locally installed gn
1719
export BUILD_TOOLS=/home/iojs/build-tools
1820
export LD_LIBRARY_PATH=$BUILD_TOOLS:$LD_LIBRARY_PATH
1921
# Avoid linking to ccache symbolic links as ccache decides which
2022
# binary to run based on the name of the link (we always name them gcc/g++).
21-
CC_PATH=`which -a $CC gcc | grep -v ccache | head -n 1`
22-
CXX_PATH=`which -a $CXX g++ | grep -v ccache | head -n 1`
23+
# shellcheck disable=SC2154
24+
CC_PATH=`command -v "$CC" gcc | grep -v ccache | head -n 1`
25+
# shellcheck disable=SC2154
26+
CXX_PATH=`command -v "$CXX" g++ | grep -v ccache | head -n 1`
2327
rm -f "$BUILD_TOOLS/g++"
2428
rm -f "$BUILD_TOOLS/gcc"
25-
ln -s $CXX_PATH "$BUILD_TOOLS/g++"
26-
ln -s $CC_PATH "$BUILD_TOOLS/gcc"
29+
ln -s "$CXX_PATH" "$BUILD_TOOLS/g++"
30+
ln -s "$CC_PATH" "$BUILD_TOOLS/gcc"
2731
export PATH=$BUILD_TOOLS:$PATH
2832

2933
g++ --version
3034
gcc --version
3135
export PKG_CONFIG_PATH=$BUILD_TOOLS/pkg-config
32-
gn gen -v out.gn/$BUILD_ARCH_TYPE --args="is_component_build=false is_debug=false use_goma=false goma_dir=\"None\" use_custom_libcxx=false v8_target_cpu=\"$TARGET_ARCH\" target_cpu=\"$TARGET_ARCH\" v8_enable_backtrace=true"
33-
ninja -v -C out.gn/$BUILD_ARCH_TYPE d8 cctest inspector-test
36+
gn gen -v "out.gn/$BUILD_ARCH_TYPE" --args="is_component_build=false is_debug=false use_goma=false goma_dir=\"None\" use_custom_libcxx=false v8_target_cpu=\"$TARGET_ARCH\" target_cpu=\"$TARGET_ARCH\" v8_enable_backtrace=true"
37+
ninja -v -C "out.gn/$BUILD_ARCH_TYPE" d8 cctest inspector-test
3438
else
35-
PATH=~/_depot_tools:$PATH tools/dev/v8gen.py $BUILD_ARCH_TYPE --no-goma $V8_BUILD_OPTIONS
36-
PATH=~/_depot_tools:$PATH ninja -C out.gn/$BUILD_ARCH_TYPE/ d8 cctest inspector-test
39+
PATH=~/_depot_tools:$PATH tools/dev/v8gen.py "$BUILD_ARCH_TYPE" --no-goma "$V8_BUILD_OPTIONS"
40+
PATH=~/_depot_tools:$PATH ninja -C "out.gn/$BUILD_ARCH_TYPE/" d8 cctest inspector-test
3741
fi

tools/osx-pkg-postinstall.sh

+1-1
Original file line numberDiff line numberDiff line change
@@ -1,6 +1,6 @@
11
#!/bin/sh
22
# TODO Can this be done inside the .pmdoc?
33
# TODO Can we extract $PREFIX from the installer?
4-
cd /usr/local/bin
4+
cd /usr/local/bin || exit
55
ln -sf ../lib/node_modules/npm/bin/npm-cli.js npm
66
ln -sf ../lib/node_modules/npm/bin/npx-cli.js npx

0 commit comments

Comments
 (0)