Add shallow syntax checking

`bash -n` will do a shallow syntax check on a bash script,
just checking that the script parses. It's possible to have
scripts that pass all bashate style checks, but don't actually
run because of a syntax error, so this adds error code E040
and reports the syntax errors that `bash -n` outputs.

Change-Id: Ib128c54493221e71cca19a497a6efc4f5fc368c1
This commit is contained in:
Nikki Heald 2015-10-06 17:24:28 +01:00
parent 1d949d6e3e
commit 77da51f026
5 changed files with 49 additions and 1 deletions

View File

@ -46,6 +46,7 @@ Obsolete, deprecated or unsafe syntax
Rules to identify obsolete, deprecated or unsafe syntax that should
not be used
- E040: Syntax errors reported by `bash -n`
- E041: Usage of $[ for arithmetic is deprecated for $((
- E042: local declaration hides errors

View File

@ -17,6 +17,7 @@ from __future__ import absolute_import
import argparse
import fileinput
import re
import subprocess
import sys
from bashate import messages
@ -111,6 +112,25 @@ def check_hashbang(line, filename, report):
report.print_error(MESSAGES['E005'].msg, line)
def check_syntax(filename, report):
# get bash to check the syntax, parse the output for line numbers
# and syntax errors to send to the report.
syntax_pattern = re.compile('^.*?: line ([0-9]+): (.*)$')
proc = subprocess.Popen(
['bash', '-n', filename], stdout=subprocess.PIPE,
stderr=subprocess.PIPE)
outputs = proc.communicate()
if proc.returncode != 0:
syntax_errors = [
line for line in outputs[1].split('\n') if 'syntax error' in line]
for line in syntax_errors:
groups = syntax_pattern.match(line).groups()
error_message = groups[1]
lineno = int(groups[0])
msg = '%s: %s' % (MESSAGES['E040'].msg, error_message)
report.print_error(msg, filename=filename, filelineno=lineno)
class BashateRun(object):
def __init__(self):
@ -143,7 +163,7 @@ class BashateRun(object):
return True
return self.warning_list and re.search(self.warning_list, error)
def print_error(self, error, line,
def print_error(self, error, line='',
filename=None, filelineno=None):
if self.should_ignore(error):
return
@ -183,6 +203,10 @@ class BashateRun(object):
report = self
for fname in files:
# simple syntax checking, as files can pass style but still cause
# syntax errors when you try to run them.
check_syntax(fname, report)
for line in fileinput.input(fname):
if fileinput.isfirstline():
# if in_multiline when the new file starts then we didn't

View File

@ -145,6 +145,15 @@ _messages = {
""",
'default': 'E'
},
'E040': {
'msg': 'Syntax error',
'long_msg':
"""
`bash -n` determined that there was a syntax error preventing
the script from parsing correctly and running.
""",
'default': 'E'
},
'E041': {
'msg': 'Arithmetic expansion using $[ is deprecated for $((',
'long_msg':

View File

@ -0,0 +1,7 @@
#!/bin/bash
function myfn {
if [ '' == '' ]; then
echo 'Things'
fii
}

View File

@ -224,6 +224,13 @@ class TestBashateSamples(base.TestCase):
self.assert_error_found('E005', 1)
def test_sample_E040(self):
test_files = ['bashate/tests/samples/E040_syntax_error.sh']
self.run.register_errors('E040')
self.run.check_files(test_files, False)
self.assert_error_found('E040', 7)
def test_sample_warning(self):
# reuse a couple of the above files to make sure we turn
# errors down to warnings if requested