aboutsummaryrefslogtreecommitdiffstats
path: root/python
diff options
context:
space:
mode:
authorJim Berlage <jberlage@mdsol.com>2016-07-05 12:15:40 -0500
committerJim Berlage <jberlage@mdsol.com>2016-07-22 11:53:14 -0500
commit7952bd00b687c3c1f5458f1750267e2bc1469ddd (patch)
tree1ecca5e37f3bf72fa2c4c24ea74abba8003fc1ea /python
parent1e0321f7dde5f33f7d26bbd4f088622fa3660477 (diff)
downloadservo-7952bd00b687c3c1f5458f1750267e2bc1469ddd.tar.gz
servo-7952bd00b687c3c1f5458f1750267e2bc1469ddd.zip
Add linting for shell scripts
This changes tidy to check shell scripts for the proper shebang and options. It does not check that variables are formatted correctly. It also adds a check for the MPL 2.0 license in shell scripts.
Diffstat (limited to 'python')
-rw-r--r--python/tidy/servo_tidy/licenseck.py8
-rw-r--r--python/tidy/servo_tidy/tidy.py42
-rw-r--r--python/tidy/servo_tidy_tests/shell_tidy.sh7
-rw-r--r--python/tidy/servo_tidy_tests/test_tidy.py6
4 files changed, 60 insertions, 3 deletions
diff --git a/python/tidy/servo_tidy/licenseck.py b/python/tidy/servo_tidy/licenseck.py
index 03113da507d..dce01460d24 100644
--- a/python/tidy/servo_tidy/licenseck.py
+++ b/python/tidy/servo_tidy/licenseck.py
@@ -24,6 +24,14 @@ licenses = [
""",
"""\
+#!/usr/bin/env bash
+
+# This Source Code Form is subject to the terms of the Mozilla Public
+# License, v. 2.0. If a copy of the MPL was not distributed with this
+# file, You can obtain one at http://mozilla.org/MPL/2.0/.
+""",
+
+"""\
#!/usr/bin/env python
# This Source Code Form is subject to the terms of the Mozilla Public
diff --git a/python/tidy/servo_tidy/tidy.py b/python/tidy/servo_tidy/tidy.py
index 873af6ca22b..34eea7f882a 100644
--- a/python/tidy/servo_tidy/tidy.py
+++ b/python/tidy/servo_tidy/tidy.py
@@ -26,7 +26,7 @@ MAX_LICENSE_LINESPAN = max(len(license.splitlines()) for license in licenses)
# File patterns to include in the non-WPT tidy check.
FILE_PATTERNS_TO_CHECK = ["*.rs", "*.rc", "*.cpp", "*.c",
- "*.h", "Cargo.lock", "*.py",
+ "*.h", "Cargo.lock", "*.py", "*.sh",
"*.toml", "*.webidl", "*.json"]
# File patterns that are ignored for all tidy and lint checks.
@@ -43,6 +43,7 @@ IGNORED_FILES = [
os.path.join(".", "tests", "wpt", "metadata", "MANIFEST.json"),
os.path.join(".", "tests", "wpt", "metadata-css", "MANIFEST.json"),
os.path.join(".", "components", "script", "dom", "webidls", "ForceTouchEvent.webidl"),
+ os.path.join(".", "support", "android", "openssl.sh"),
# FIXME(pcwalton, #11679): This is a workaround for a tidy error on the quoted string
# `"__TEXT,_info_plist"` inside an attribute.
os.path.join(".", "components", "servo", "platform", "macos", "mod.rs"),
@@ -169,7 +170,11 @@ def check_modeline(file_name, lines):
def check_length(file_name, idx, line):
if file_name.endswith(".lock") or file_name.endswith(".json"):
raise StopIteration
- max_length = 120
+ # Prefer shorter lines when shell scripting.
+ if file_name.endswith(".sh"):
+ max_length = 80
+ else:
+ max_length = 120
if len(line.rstrip('\n')) > max_length:
yield (idx + 1, "Line is longer than %d characters" % max_length)
@@ -306,6 +311,36 @@ def check_toml(file_name, lines):
yield (0, ".toml file should contain a valid license.")
+def check_shell(file_name, lines):
+ if not file_name.endswith(".sh"):
+ raise StopIteration
+
+ shebang = "#!/usr/bin/env bash"
+ required_options = {"set -o errexit", "set -o nounset", "set -o pipefail"}
+
+ if len(lines) == 0:
+ yield (0, 'script is an empty file')
+ else:
+ if lines[0].rstrip() != shebang:
+ yield (1, 'script does not have shebang "{}"'.format(shebang))
+
+ for idx in range(1, len(lines)):
+ stripped = lines[idx].rstrip()
+
+ # Comments or blank lines are ignored. (Trailing whitespace is caught with a separate linter.)
+ if lines[idx].startswith("#") or stripped == "":
+ continue
+ elif stripped in required_options:
+ required_options.remove(stripped)
+ else:
+ # The first non-comment, non-whitespace, non-option line is the first "real" line of the script.
+ # The shebang, options, etc. must come before this.
+ if len(required_options) != 0:
+ formatted = ['"{}"'.format(opt) for opt in required_options]
+ yield (idx + 1, "script is missing options {}".format(", ".join(formatted)))
+ break
+
+
def check_rust(file_name, lines):
if not file_name.endswith(".rs") or \
file_name.endswith(".mako.rs") or \
@@ -694,7 +729,8 @@ def scan(only_changed_files=False, progress=True):
# standard checks
files_to_check = filter_files('.', only_changed_files, progress)
checking_functions = (check_flake8, check_lock, check_webidl_spec, check_json)
- line_checking_functions = (check_license, check_by_line, check_toml, check_rust, check_spec, check_modeline)
+ line_checking_functions = (check_license, check_by_line, check_toml, check_shell,
+ check_rust, check_spec, check_modeline)
errors = collect_errors_for_files(files_to_check, checking_functions, line_checking_functions)
# check dependecy licenses
dep_license_errors = check_dep_license_errors(get_dep_toml_files(only_changed_files), progress)
diff --git a/python/tidy/servo_tidy_tests/shell_tidy.sh b/python/tidy/servo_tidy_tests/shell_tidy.sh
new file mode 100644
index 00000000000..fbf0e784755
--- /dev/null
+++ b/python/tidy/servo_tidy_tests/shell_tidy.sh
@@ -0,0 +1,7 @@
+#!/bin/bash
+#
+# Tests tidy for shell scripts.
+
+set -o nounset
+
+echo "hello world"
diff --git a/python/tidy/servo_tidy_tests/test_tidy.py b/python/tidy/servo_tidy_tests/test_tidy.py
index 8db19a5efd7..3ad7e03d667 100644
--- a/python/tidy/servo_tidy_tests/test_tidy.py
+++ b/python/tidy/servo_tidy_tests/test_tidy.py
@@ -48,6 +48,12 @@ class CheckTidiness(unittest.TestCase):
self.assertEqual('incorrect license', errors.next()[2])
self.assertNoMoreErrors(errors)
+ def test_shell(self):
+ errors = tidy.collect_errors_for_files(iterFile('shell_tidy.sh'), [], [tidy.check_shell], print_text=False)
+ self.assertEqual('script does not have shebang "#!/usr/bin/env bash"', errors.next()[2])
+ self.assertEqual('script is missing options "set -o errexit", "set -o pipefail"', errors.next()[2])
+ self.assertNoMoreErrors(errors)
+
def test_rust(self):
errors = tidy.collect_errors_for_files(iterFile('rust_tidy.rs'), [], [tidy.check_rust], print_text=False)
self.assertEqual('use statement spans multiple lines', errors.next()[2])