# 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/.
import os import sys from argparse import REMAINDER, SUPPRESS, ArgumentParser from pathlib import Path
from mozlint.errors import NoValidLinter from mozlint.formatters import all_formatters
class MozlintParser(ArgumentParser):
arguments = [
[
["paths"],
{ "nargs": "*", "default": None, "help": "Paths to file or directories to lint, like " "'browser/components/loop' or 'mobile/android'. " "If not provided, defaults to the files changed according " "to --outgoing and --workdir.",
},
],
[
["-l", "--linter"],
{ "dest": "linters", "default": [], "action": "append", "help": "Linters to run, e.g 'eslint'. By default all linters " "are run for all the appropriate files.",
},
],
[
["--list"],
{ "dest": "list_linters", "default": False, "action": "store_true", "help": "List all available linters and exit.",
},
],
[
["-W", "--warnings"],
{ "const": True, "nargs": "?", "choices": ["soft"], "dest": "show_warnings", "help": "Display and fail on warnings in addition to errors. " "--warnings=soft can be used to report warnings but only fail " "on errors.",
},
],
[
["-v", "--verbose"],
{ "dest": "show_verbose", "default": False, "action": "store_true", "help": "Enable verbose logging.",
},
],
[
["-f", "--format"],
{ "dest": "formats", "action": "append", "help": "Formatter to use. Defaults to 'stylish' on stdout. " "You can specify an optional path as --format formatter:path " "that will be used instead of stdout. " "You can also use multiple formatters at the same time. " "Formatters available: {}.".format(", ".join(all_formatters.keys())),
},
],
[
["-n", "--no-filter"],
{ "dest": "use_filters", "default": True, "action": "store_false", "help": "Ignore all filtering. This is useful for quickly " "testing a directory that otherwise wouldn't be run, " "without needing to modify the config file.",
},
],
[
["--include-third-party"],
{ "dest": "include_third-party", "default": False, "action": "store_true", "help": "Also run the linter(s) on third-party code",
},
],
[
["-o", "--outgoing"],
{ "const": True, "nargs": "?", "help": "Lint files touched by commits that are not on the remote repository. " "Without arguments, finds the default remote that would be pushed to. " "The remote branch can also be specified manually. Works with " "mercurial or git.",
},
],
[
["-w", "--workdir"],
{ "const": "all", "nargs": "?", "choices": ["staged", "all"], "help": "Lint files touched by changes in the working directory " "(i.e haven't been committed yet). On git, --workdir=staged " "can be used to only consider staged files. Works with " "mercurial or git.",
},
],
[
["-r", "--rev"],
{ "default": None, "type": str, "help": "Lint files touched by changes in revisions described by REV. " "For mercurial, it may be any revset. For git, it is a single tree-ish.",
},
],
[
["--fix"],
{ "action": "store_true", "default": False, "help": "Fix lint errors if possible. Any errors that could not be fixed " "will be printed as normal.",
},
],
[
["--edit"],
{ "action": "store_true", "default": False, "help": "Each file containing lint errors will be opened in $EDITOR one after " "the other.",
},
],
[
["--setup"],
{ "action": "store_true", "default": False, "help": "Bootstrap linter dependencies without running any of the linters.",
},
],
[
["-j", "--jobs"],
{ "default": None, "dest": "num_procs", "type": int, "help": "Number of worker processes to spawn when running linters. " "Defaults to the number of cores in your CPU.",
},
], # Paths to check for linter configurations. # Default: tools/lint set in tools/lint/mach_commands.py
[
["--config-path"],
{ "action": "append", "default": [], "dest": "config_paths", "help": SUPPRESS,
},
],
[
["--check-exclude-list"],
{ "dest": "check_exclude_list", "default": False, "action": "store_true", "help": "Run linters for all the paths in the exclude list.",
},
],
[
["extra_args"],
{ "nargs": REMAINDER, "help": "Extra arguments that will be forwarded to the underlying linter.",
},
],
]
for cli, args in self.arguments:
self.add_argument(*cli, **args)
def parse_known_args(self, *args, **kwargs): # Allow '-wo' or '-ow' as shorthand for both --workdir and --outgoing. for token in ("-wo", "-ow"): if token in args[0]:
i = args[0].index(token)
args[0].pop(i)
args[0][i:i] = [token[:2], "-" + token[2]]
# This is here so the eslint mach command doesn't lose 'extra_args' # when using mach's dispatch functionality.
args, extra = ArgumentParser.parse_known_args(self, *args, **kwargs)
args.extra_args = extra
self.validate(args) return args, extra
def validate(self, args): if args.edit andnot os.environ.get("EDITOR"):
self.error("must set the $EDITOR environment variable to use --edit")
if args.paths:
invalid = [p for p in args.paths ifnot os.path.exists(p)] if invalid:
self.error( "the following paths do not exist:\n{}".format("\n".join(invalid))
)
if args.formats:
formats = [] for fmt in args.formats: if isinstance(fmt, tuple): # format is already processed
formats.append(fmt) continue
# Check path is writable
fmt_dir = os.path.dirname(path) ifnot os.access(fmt_dir, os.W_OK | os.X_OK):
self.error( "the following directory is not writable: {}".format(
fmt_dir
)
)
if fmt notin all_formatters.keys():
self.error( "the following formatter is not available: {}".format(fmt)
)
formats.append((fmt, path))
args.formats = formats else: # Can't use argparse default or this choice will be always present
args.formats = [("stylish", None)]
def find_linters(config_paths, linters=None):
lints = {} for search_path in config_paths: ifnot os.path.isdir(search_path): continue
sys.path.insert(0, search_path)
files = os.listdir(search_path) for f in files:
name = os.path.basename(f)
def get_exclude_list_output(result, paths): # Store the paths of all the subdirectories leading to the error files
error_file_paths = set() for issues in result.issues.values():
error_file = issues[0].relpath
error_file_paths.add(error_file)
parent_dir = os.path.dirname(error_file) while parent_dir:
error_file_paths.add(parent_dir)
parent_dir = os.path.dirname(parent_dir)
paths = [os.path.dirname(path) if path[-1] == "/"else path for path in paths] # Remove all the error paths to get the list of green paths
green_paths = sorted(set(paths).difference(error_file_paths))
if green_paths:
out = ( "The following list of paths are now green " "and can be removed from the exclude list:\n\n"
)
out += "\n".join(green_paths)
else:
out = "No path in the exclude list is green."
lintargs["config_paths"] = [
os.path.join(lintargs["root"], p) for p in lintargs["config_paths"]
]
# Always perform exhaustive linting for exclude list paths
lintargs["use_filters"] = lintargs["use_filters"] andnot check_exclude_list
if list_linters:
lint_paths = find_linters(lintargs["config_paths"], linters)
linters = [
os.path.splitext(os.path.basename(l))[0] for l in lint_paths["lint_paths"]
]
print("\n".join(sorted(linters)))
print( "\nNote that clang-tidy checks are not run as part of this " "command, but using the static-analysis command."
) return 0
lint = LintRoller(setupargs=setupargs or {}, **lintargs)
linters_info = find_linters(lintargs["config_paths"], linters)
result = None
try:
lint.read(linters_info["lint_paths"])
if check_exclude_list: if len(lint.linters) > 1:
print("error: specify a single linter to check with `-l/--linter`") return 1
paths = lint.linters[0]["local_exclude"]
if ( not paths and Path.cwd() == Path(lint.root) andnot (outgoing or workdir or rev)
):
print( "warning: linting the entire repo takes a long time, using --outgoing and " "--workdir instead. If you want to lint the entire repo, run `./mach lint .`"
) # Setting the default values
outgoing = True
workdir = "all"
# Always run bootstrapping, but return early if --setup was passed in.
ret = lint.setup(virtualenv_manager=virtualenv_manager) if setup: return ret
if linters_info["linters_not_found"] != []: raise NoValidLinter
# run all linters
result = lint.roll(
paths, outgoing=outgoing, workdir=workdir, rev=rev, num_procs=num_procs
) except NoValidLinter as e:
result = lint.result
print(str(e))
if edit and result.issues:
edit_issues(result)
result = lint.roll(result.issues.keys(), num_procs=num_procs)
for every in linters_info["linters_not_found"]:
result.failed_setup.add(every)
if check_exclude_list: # Get and display all those paths in the exclude list which are # now green and can be safely removed from the list
out = get_exclude_list_output(result, paths)
print(out, file=sys.stdout) return result.returncode
for formatter_name, path in formats:
formatter = formatters.get(formatter_name)
out = formatter(result) # We do this only for `json` that is mostly used in automation ifnot out and formatter_name == "json":
out = "{}"
if out:
fh = open(path, "w") if path else sys.stdout
ifnot path and fh.encoding == "ascii": # If sys.stdout.encoding is ascii, printing output will fail # due to the stylish formatter's use of unicode characters. # Ideally the user should fix their environment by setting # `LC_ALL=C.UTF-8` or similar. But this is a common enough # problem that we help them out a little here by manually # encoding and writing to the stdout buffer directly.
out += "\n"
fh.buffer.write(out.encode("utf-8", errors="replace"))
fh.buffer.flush() else:
print(out, file=fh)
Die Informationen auf dieser Webseite wurden
nach bestem Wissen sorgfältig zusammengestellt. Es wird jedoch weder Vollständigkeit, noch Richtigkeit,
noch Qualität der bereit gestellten Informationen zugesichert.
Bemerkung:
Die farbliche Syntaxdarstellung ist noch experimentell.