summaryrefslogtreecommitdiffstats
path: root/python/mozlint
diff options
context:
space:
mode:
Diffstat (limited to 'python/mozlint')
-rw-r--r--python/mozlint/mozlint/parser.py11
-rw-r--r--python/mozlint/mozlint/pathutils.py45
-rw-r--r--python/mozlint/mozlint/types.py1
-rw-r--r--python/mozlint/test/linters/invalid_ext_and_exclude_ext.yml7
-rw-r--r--python/mozlint/test/test_parser.py1
-rw-r--r--python/mozlint/test/test_pathutils.py68
6 files changed, 118 insertions, 15 deletions
diff --git a/python/mozlint/mozlint/parser.py b/python/mozlint/mozlint/parser.py
index eac502495b..213af88b4b 100644
--- a/python/mozlint/mozlint/parser.py
+++ b/python/mozlint/mozlint/parser.py
@@ -91,8 +91,15 @@ class Parser(object):
"form 'module:object'".format(linter["setup"]),
)
- if "extensions" in linter:
- linter["extensions"] = [e.strip(".") for e in linter["extensions"]]
+ if "extensions" in linter and "exclude_extensions" in linter:
+ raise LinterParseError(
+ relpath,
+ "Can't have both 'extensions' and 'exclude_extensions'!",
+ )
+
+ for prop in ["extensions", "exclude_extensions"]:
+ if prop in linter:
+ linter[prop] = [e.strip(".") for e in linter[prop]]
def parse(self, path):
"""Read a linter and return its LINTER definition.
diff --git a/python/mozlint/mozlint/pathutils.py b/python/mozlint/mozlint/pathutils.py
index 9b46fa6d41..eabfbafb8c 100644
--- a/python/mozlint/mozlint/pathutils.py
+++ b/python/mozlint/mozlint/pathutils.py
@@ -139,16 +139,20 @@ def collapse(paths, base=None, dotfiles=False):
return list(covered)
-def filterpaths(root, paths, include, exclude=None, extensions=None):
+def filterpaths(
+ root, paths, include, exclude=None, extensions=None, exclude_extensions=None
+):
"""Filters a list of paths.
Given a list of paths and some filtering rules, return the set of paths
- that should be linted.
+ that should be linted. Note that at most one of extensions or
+ exclude_extensions should be provided (ie not both).
:param paths: A starting list of paths to possibly lint.
:param include: A list of paths that should be included (required).
:param exclude: A list of paths that should be excluded (optional).
:param extensions: A list of file extensions which should be considered (optional).
+ :param exclude_extensions: A list of file extensions which should not be considered (optional).
:returns: A tuple containing a list of file paths to lint and a list of
paths to exclude.
"""
@@ -173,6 +177,8 @@ def filterpaths(root, paths, include, exclude=None, extensions=None):
# Exclude bad file extensions
if extensions and path.isfile and path.ext not in extensions:
continue
+ elif exclude_extensions and path.isfile and path.ext in exclude_extensions:
+ continue
if path.match(excludeglobs):
continue
@@ -280,6 +286,9 @@ def expand_exclusions(paths, config, root):
Generator which generates list of paths that weren't excluded.
"""
extensions = [e.lstrip(".") for e in config.get("extensions", [])]
+ exclude_extensions = [e.lstrip(".") for e in config.get("exclude_extensions", [])]
+ if extensions and exclude_extensions:
+ raise ValueError("Can't specify both extensions and exclude_extensions.")
find_dotfiles = config.get("find-dotfiles", False)
def normalize(path):
@@ -289,6 +298,13 @@ def expand_exclusions(paths, config, root):
return mozpath.join(root, path)
exclude = list(map(normalize, config.get("exclude", [])))
+ # We need excluded extensions in both the ignore for the FileFinder and in
+ # the exclusion set. If we don't put it in the exclusion set, we would
+ # return files that are passed explicitly and whose extensions are in the
+ # exclusion set. If we don't put it in the ignore set, the FileFinder
+ # would return files in (sub)directories passed to us.
+ base_ignore = ["**/*.{}".format(ext) for ext in exclude_extensions]
+ exclude += base_ignore
for path in paths:
path = mozpath.normsep(path)
if os.path.isfile(path):
@@ -301,16 +317,27 @@ def expand_exclusions(paths, config, root):
yield path
continue
- ignore = [
+ # If there are neither extensions nor exclude_extensions, we can't do
+ # anything useful with a directory. Skip:
+ if not extensions and not exclude_extensions:
+ continue
+
+ # This is a directory. Check we don't have excludes for ancestors of
+ # this path. Mess with slashes to avoid "foo/bar" matching "foo/barry".
+ parent_path = os.path.dirname(path.rstrip("/")) + "/"
+ assert not any(parent_path.startswith(e.rstrip("/") + "/") for e in exclude)
+
+ ignore = base_ignore + [
e[len(path) :].lstrip("/")
for e in exclude
if mozpath.commonprefix((path, e)) == path
]
- finder = FileFinder(path, ignore=ignore, find_dotfiles=find_dotfiles)
-
- _, ext = os.path.splitext(path)
- ext.lstrip(".")
- for ext in extensions:
- for p, f in finder.find("**/*.{}".format(ext)):
+ finder = FileFinder(path, ignore=ignore, find_dotfiles=find_dotfiles)
+ if extensions:
+ for ext in extensions:
+ for p, f in finder.find("**/*.{}".format(ext)):
+ yield os.path.join(path, p)
+ else:
+ for p, f in finder.find("**/*.*"):
yield os.path.join(path, p)
diff --git a/python/mozlint/mozlint/types.py b/python/mozlint/mozlint/types.py
index 1a9a0bd473..468e28d81a 100644
--- a/python/mozlint/mozlint/types.py
+++ b/python/mozlint/mozlint/types.py
@@ -39,6 +39,7 @@ class BaseType(object):
config["include"],
config.get("exclude", []),
config.get("extensions", []),
+ config.get("exclude_extensions", []),
)
config["exclude"] = exclude
elif config.get("exclude"):
diff --git a/python/mozlint/test/linters/invalid_ext_and_exclude_ext.yml b/python/mozlint/test/linters/invalid_ext_and_exclude_ext.yml
new file mode 100644
index 0000000000..a73353fb71
--- /dev/null
+++ b/python/mozlint/test/linters/invalid_ext_and_exclude_ext.yml
@@ -0,0 +1,7 @@
+---
+InvalidExtensionAndExcludedExtensions:
+ type: string
+ payload: foobar
+ description: Having both extensions and exclude_extensions is not allowed.
+ extensions: ["*.js"]
+ exclude_extensions: ["*.png"]
diff --git a/python/mozlint/test/test_parser.py b/python/mozlint/test/test_parser.py
index 2fbf26c8e5..3cb319a2e0 100644
--- a/python/mozlint/test/test_parser.py
+++ b/python/mozlint/test/test_parser.py
@@ -59,6 +59,7 @@ def test_parser_valid_multiple(parse):
"invalid_include_with_glob.yml",
"invalid_exclude.yml",
"invalid_support_files.yml",
+ "invalid_ext_and_exclude_ext.yml",
"missing_attrs.yml",
"missing_definition.yml",
"non_existing_include.yml",
diff --git a/python/mozlint/test/test_pathutils.py b/python/mozlint/test/test_pathutils.py
index 78f7883e88..5f593fdc47 100644
--- a/python/mozlint/test/test_pathutils.py
+++ b/python/mozlint/test/test_pathutils.py
@@ -59,6 +59,43 @@ def assert_paths(a, b):
"extensions": ["py"],
"expected": ["a.py", "subdir1/b.py"],
},
+ pytest.param(
+ {
+ "paths": [
+ "a.py",
+ "a.js",
+ "subdir1/b.py",
+ "subdir2/c.py",
+ "subdir1/subdir3/d.py",
+ ],
+ "include": ["."],
+ "exclude": [],
+ "exclude_extensions": ["py"],
+ "expected": ["a.js"],
+ },
+ id="Excluding .py should only return .js file.",
+ ),
+ pytest.param(
+ {
+ "paths": [
+ "a.py",
+ "a.js",
+ "subdir1/b.py",
+ "subdir2/c.py",
+ "subdir1/subdir3/d.py",
+ ],
+ "include": ["."],
+ "exclude": [],
+ "exclude_extensions": ["js"],
+ "expected": [
+ "a.py",
+ "subdir1/b.py",
+ "subdir2/c.py",
+ "subdir1/subdir3/d.py",
+ ],
+ },
+ id="Excluding .js should only return .py files.",
+ ),
{
"paths": ["a.py", "a.js", "subdir2"],
"include": ["."],
@@ -104,24 +141,47 @@ def test_filterpaths(test):
"paths": ["subdir1/b.js"],
"config": {
"exclude": ["subdir1"],
- "extensions": "js",
+ "extensions": ["js"],
},
"expected": [],
},
{
- "paths": ["subdir1/subdir3"],
+ "paths": ["subdir1"],
"config": {
"exclude": ["subdir1"],
- "extensions": "js",
+ "extensions": ["js"],
},
"expected": [],
},
+ pytest.param(
+ {
+ "paths": ["a.py", "subdir1"],
+ "config": {
+ "exclude": ["subdir1"],
+ "exclude_extensions": ["gob"],
+ },
+ "expected": ["a.py"],
+ },
+ id="Excluding both subdirs and nonsense extensions returns other files.",
+ ),
+ pytest.param(
+ {
+ "paths": ["a.py", "a.js", "subdir1"],
+ "config": {
+ "exclude": [],
+ "exclude_extensions": ["py"],
+ },
+ "expected": ["a.js", "subdir1/subdir3/d.js", "subdir1/b.js"],
+ },
+ id="Excluding .py files returns only non-.py files, also from subdirs.",
+ ),
),
)
def test_expand_exclusions(test):
expected = test.pop("expected", [])
- paths = list(pathutils.expand_exclusions(test["paths"], test["config"], root))
+ input_paths = [os.path.join(root, p) for p in test["paths"]]
+ paths = list(pathutils.expand_exclusions(input_paths, test["config"], root))
assert_paths(paths, expected)