fix: enforce consistent shell redirection format (#1533)

This commit is contained in:
Edwin Kofler 2023-04-10 20:12:08 -07:00 committed by GitHub
parent a1bc0e97cd
commit 1bc205e8aa
No known key found for this signature in database
GPG Key ID: 4AEE18F83AFDEB23
3 changed files with 59 additions and 15 deletions

View File

@ -11,7 +11,7 @@ remove_shim_for_version() {
local count_installed local count_installed
count_installed=$(list_installed_versions "$plugin_name" | wc -l) count_installed=$(list_installed_versions "$plugin_name" | wc -l)
if ! grep -x "# asdf-plugin: $plugin_name $version" "$shim_path" >/dev/null 2>&1; then if ! grep -x "# asdf-plugin: $plugin_name $version" "$shim_path" &>/dev/null; then
return 0 return 0
fi fi

View File

@ -25,9 +25,9 @@ install_command() {
} }
get_concurrency() { get_concurrency() {
if command -v nproc >/dev/null 2>&1; then if command -v nproc &>/dev/null; then
nproc nproc
elif command -v sysctl >/dev/null 2>&1 && sysctl hw.ncpu >/dev/null 2>&1; then elif command -v sysctl &>/dev/null && sysctl hw.ncpu &>/dev/null; then
sysctl -n hw.ncpu sysctl -n hw.ncpu
elif [ -f /proc/cpuinfo ]; then elif [ -f /proc/cpuinfo ]; then
grep -c processor /proc/cpuinfo grep -c processor /proc/cpuinfo

View File

@ -80,17 +80,41 @@ def noFunctionKeywordFixer(line: str, m: Any) -> str:
return f'{prestr}{midstr}() {poststr}' return f'{prestr}{midstr}() {poststr}'
def lintfile(filepath: Path, rules: List[Rule], options: Dict[str, Any]): # Before: >/dev/null 2>&1
content_arr = filepath.read_text().split('\n') # After: &>/dev/null
# ---
# Before: 2>/dev/null 1>&2
# After: &>/dev/null
def noVerboseRedirectionFixer(line: str, m: Any) -> str:
prestr, _, poststr = utilGetStrs(line, m)
return f'{prestr}&>/dev/null{poststr}'
def lintfile(file: Path, rules: List[Rule], options: Dict[str, Any]):
content_arr = file.read_text().split('\n')
for line_i, line in enumerate(content_arr): for line_i, line in enumerate(content_arr):
if 'checkstyle-ignore' in line: if 'checkstyle-ignore' in line:
continue continue
for rule in rules: for rule in rules:
should_run = False
if 'sh' in rule['fileTypes']:
if file.name.endswith('.sh') or str(file.absolute()).endswith('bin/asdf'):
should_run = True
if 'bash' in rule['fileTypes']:
if file.name.endswith('.bash') or file.name.endswith('.bats'):
should_run = True
if options['verbose']:
print(f'{str(file)}: {should_run}')
if not should_run:
continue
m = re.search(rule['regex'], line) m = re.search(rule['regex'], line)
if m is not None and m.group('match') is not None: if m is not None and m.group('match') is not None:
dir = os.path.relpath(filepath.resolve(), Path.cwd()) dir = os.path.relpath(file.resolve(), Path.cwd())
prestr = line[0:m.start('match')] prestr = line[0:m.start('match')]
midstr = line[m.start('match'):m.end('match')] midstr = line[m.start('match'):m.end('match')]
poststr = line[m.end('match'):] poststr = line[m.end('match'):]
@ -106,7 +130,7 @@ def lintfile(filepath: Path, rules: List[Rule], options: Dict[str, Any]):
rule['found'] += 1 rule['found'] += 1
if options['fix']: if options['fix']:
filepath.write_text('\n'.join(content_arr)) file.write_text('\n'.join(content_arr))
def main(): def main():
rules: List[Rule] = [ rules: List[Rule] = [
@ -114,6 +138,7 @@ def main():
'name': 'no-double-backslash', 'name': 'no-double-backslash',
'regex': '".*?(?P<match>\\\\\\\\[abeEfnrtv\'"?xuUc]).*?(?<!\\\\)"', 'regex': '".*?(?P<match>\\\\\\\\[abeEfnrtv\'"?xuUc]).*?(?<!\\\\)"',
'reason': 'Backslashes are only required if followed by a $, `, ", \\, or <newline>', 'reason': 'Backslashes are only required if followed by a $, `, ", \\, or <newline>',
'fileTypes': ['bash', 'sh'],
'fixerFn': noDoubleBackslashFixer, 'fixerFn': noDoubleBackslashFixer,
'testPositiveMatches': [ 'testPositiveMatches': [
'printf "%s\\\\n" "Hai"', 'printf "%s\\\\n" "Hai"',
@ -123,12 +148,12 @@ def main():
'printf "%s\\n" "Hai"', 'printf "%s\\n" "Hai"',
'echo -n "Hello\\n"' 'echo -n "Hello\\n"'
], ],
'found': 0
}, },
{ {
'name': 'no-pwd-capture', 'name': 'no-pwd-capture',
'regex': '(?P<match>\\$\\(pwd\\))', 'regex': '(?P<match>\\$\\(pwd\\))',
'reason': '$PWD is essentially equivalent to $(pwd) without the overhead of a subshell', 'reason': '$PWD is essentially equivalent to $(pwd) without the overhead of a subshell',
'fileTypes': ['bash', 'sh'],
'fixerFn': noPwdCaptureFixer, 'fixerFn': noPwdCaptureFixer,
'testPositiveMatches': [ 'testPositiveMatches': [
'$(pwd)' '$(pwd)'
@ -136,12 +161,12 @@ def main():
'testNegativeMatches': [ 'testNegativeMatches': [
'$PWD' '$PWD'
], ],
'found': 0
}, },
{ {
'name': 'no-test-double-equals', 'name': 'no-test-double-equals',
'regex': '(?<!\\[)\\[ (?:[^]]|](?=}))*?(?P<match>==).*?]', 'regex': '(?<!\\[)\\[ (?:[^]]|](?=}))*?(?P<match>==).*?]',
'reason': 'Disallow double equals in places where they are not necessary for consistency', 'reason': 'Disallow double equals in places where they are not necessary for consistency',
'fileTypes': ['bash', 'sh'],
'fixerFn': noTestDoubleEqualsFixer, 'fixerFn': noTestDoubleEqualsFixer,
'testPositiveMatches': [ 'testPositiveMatches': [
'[ a == b ]', '[ a == b ]',
@ -156,12 +181,12 @@ def main():
'[[ "${lines[0]}" == \'usage: \'* ]]', '[[ "${lines[0]}" == \'usage: \'* ]]',
'[ "${lines[0]}" = blah ]', '[ "${lines[0]}" = blah ]',
], ],
'found': 0
}, },
{ {
'name': 'no-function-keyword', 'name': 'no-function-keyword',
'regex': '^[ \\t]*(?P<match>function .*?(?:\\([ \\t]*\\))?[ \\t]*){', 'regex': '^[ \\t]*(?P<match>function .*?(?:\\([ \\t]*\\))?[ \\t]*){',
'reason': 'Only allow functions declared like `fn_name() {{ :; }}` for consistency (see ' + c.LINK('https://www.shellcheck.net/wiki/SC2113', 'ShellCheck SC2113') + ')', 'reason': 'Only allow functions declared like `fn_name() {{ :; }}` for consistency (see ' + c.LINK('https://www.shellcheck.net/wiki/SC2113', 'ShellCheck SC2113') + ')',
'fileTypes': ['bash', 'sh'],
'fixerFn': noFunctionKeywordFixer, 'fixerFn': noFunctionKeywordFixer,
'testPositiveMatches': [ 'testPositiveMatches': [
'function fn() { :; }', 'function fn() { :; }',
@ -170,13 +195,29 @@ def main():
'testNegativeMatches': [ 'testNegativeMatches': [
'fn() { :; }', 'fn() { :; }',
], ],
'found': 0 },
{
'name': 'no-verbose-redirection',
'regex': '(?P<match>(>/dev/null 2>&1|2>/dev/null 1>&2))',
'reason': 'Use `&>/dev/null` instead of `>/dev/null 2>&1` or `2>/dev/null 1>&2` for consistency',
'fileTypes': ['bash'],
'fixerFn': noVerboseRedirectionFixer,
'testPositiveMatches': [
'echo woof >/dev/null 2>&1',
'echo woof 2>/dev/null 1>&2',
],
'testNegativeMatches': [
'echo woof &>/dev/null',
'echo woof >&/dev/null',
],
}, },
] ]
[rule.update({ 'found': 0 }) for rule in rules]
parser = argparse.ArgumentParser() parser = argparse.ArgumentParser()
parser.add_argument('files', metavar='FILES', nargs='*') parser.add_argument('files', metavar='FILES', nargs='*')
parser.add_argument('--fix', action='store_true') parser.add_argument('--fix', action='store_true')
parser.add_argument('--verbose', action='store_true')
parser.add_argument('--internal-test-regex', action='store_true') parser.add_argument('--internal-test-regex', action='store_true')
args = parser.parse_args() args = parser.parse_args()
@ -199,7 +240,8 @@ def main():
return return
options = { options = {
'fix': args.fix 'fix': args.fix,
'verbose': args.verbose,
} }
# parse files and print matched lints # parse files and print matched lints
@ -210,9 +252,11 @@ def main():
lintfile(p, rules, options) lintfile(p, rules, options)
else: else:
for file in Path.cwd().glob('**/*'): for file in Path.cwd().glob('**/*'):
if file.name.endswith('.bash') or file.name.endswith('.sh') or file.name.endswith('.bats') or str(file.absolute()).endswith('bin/asdf'): if '.git' in str(file.absolute()):
if file.is_file(): continue
lintfile(file, rules, options)
if file.is_file():
lintfile(file, rules, options)
# print final results # print final results
print(f'{c.UNDERLINE}TOTAL ISSUES{c.RESET}') print(f'{c.UNDERLINE}TOTAL ISSUES{c.RESET}')