diff options
author | Jim Berlage <jberlage@mdsol.com> | 2016-07-05 12:15:40 -0500 |
---|---|---|
committer | Jim Berlage <jberlage@mdsol.com> | 2016-07-22 11:53:14 -0500 |
commit | 7952bd00b687c3c1f5458f1750267e2bc1469ddd (patch) | |
tree | 1ecca5e37f3bf72fa2c4c24ea74abba8003fc1ea /python | |
parent | 1e0321f7dde5f33f7d26bbd4f088622fa3660477 (diff) | |
download | servo-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.py | 8 | ||||
-rw-r--r-- | python/tidy/servo_tidy/tidy.py | 42 | ||||
-rw-r--r-- | python/tidy/servo_tidy_tests/shell_tidy.sh | 7 | ||||
-rw-r--r-- | python/tidy/servo_tidy_tests/test_tidy.py | 6 |
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]) |