# 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 argparse import copy import os from mach.decorators import Command, CommandArgument from mozbuild.base import BuildEnvironmentNotFoundException from mozbuild.base import MachCommandConditions as conditions here = os.path.abspath(os.path.dirname(__file__)) EXCLUSION_FILES = [ os.path.join("tools", "rewriting", "Generated.txt"), os.path.join("tools", "rewriting", "ThirdPartyPaths.txt"), ] EXCLUSION_FILES_OPTIONAL = [] thunderbird_excludes = os.path.join("comm", "tools", "lint", "GlobalExclude.txt") if os.path.exists(thunderbird_excludes): EXCLUSION_FILES_OPTIONAL.append(thunderbird_excludes) GLOBAL_EXCLUDES = ["**/node_modules", "tools/lint/test/files", ".hg", ".git"] VALID_FORMATTERS = {"black", "clang-format", "rustfmt", "isort"} VALID_ANDROID_FORMATTERS = {"android-format"} # Code-review bot must index issues from the whole codebase when pushing # to autoland or try repositories. In such cases, output warnings in the # task's JSON artifact but do not fail if only warnings are found. REPORT_WARNINGS = os.environ.get("GECKO_HEAD_REPOSITORY", "").rstrip("/") in ( "https://hg.mozilla.org/mozilla-central", "https://hg.mozilla.org/integration/autoland", "https://hg.mozilla.org/try", ) def setup_argument_parser(): from mozlint import cli return cli.MozlintParser() def get_global_excludes(**lintargs): # exclude misc paths excludes = GLOBAL_EXCLUDES[:] topsrcdir = lintargs["root"] # exclude top level paths that look like objdirs excludes.extend( [ name for name in os.listdir(topsrcdir) if name.startswith("obj") and os.path.isdir(name) ] ) if lintargs.get("include_thirdparty"): # For some linters, we want to include the thirdparty code too. # Example: trojan-source linter should run also on third party code. return excludes for path in EXCLUSION_FILES + EXCLUSION_FILES_OPTIONAL: with open(os.path.join(topsrcdir, path), "r") as fh: excludes.extend([f.strip() for f in fh.readlines()]) return excludes @Command( "lint", category="devenv", description="Run linters.", parser=setup_argument_parser, virtualenv_name="lint", ) def lint(command_context, *runargs, **lintargs): """Run linters.""" command_context.activate_virtualenv() from mozlint import cli, parser try: buildargs = {} buildargs["substs"] = copy.deepcopy(dict(command_context.substs)) buildargs["defines"] = copy.deepcopy(dict(command_context.defines)) buildargs["topobjdir"] = command_context.topobjdir lintargs.update(buildargs) except BuildEnvironmentNotFoundException: pass lintargs.setdefault("root", command_context.topsrcdir) lintargs["exclude"] = get_global_excludes(**lintargs) lintargs["config_paths"].insert(0, here) lintargs["virtualenv_bin_path"] = command_context.virtualenv_manager.bin_path lintargs["virtualenv_manager"] = command_context.virtualenv_manager if REPORT_WARNINGS and lintargs.get("show_warnings") is None: lintargs["show_warnings"] = "soft" for path in EXCLUSION_FILES: parser.GLOBAL_SUPPORT_FILES.append( os.path.join(command_context.topsrcdir, path) ) setupargs = { "mach_command_context": command_context, } return cli.run(*runargs, setupargs=setupargs, **lintargs) @Command( "eslint", category="devenv", description="Run eslint or help configure eslint for optimal development.", ) @CommandArgument( "paths", default=None, nargs="*", help="Paths to file or directories to lint, like " "'browser/' Defaults to the " "current directory if not given.", ) @CommandArgument( "-s", "--setup", default=False, action="store_true", help="Configure eslint for optimal development.", ) @CommandArgument("-b", "--binary", default=None, help="Path to eslint binary.") @CommandArgument( "--fix", default=False, action="store_true", help="Request that eslint automatically fix errors, where possible.", ) @CommandArgument( "--rule", default=[], dest="rules", action="append", help="Specify an additional rule for ESLint to run, e.g. 'no-new-object: error'", ) @CommandArgument( "extra_args", nargs=argparse.REMAINDER, help="Extra args that will be forwarded to eslint.", ) def eslint(command_context, paths, extra_args=[], **kwargs): command_context._mach_context.commands.dispatch( "lint", command_context._mach_context, linters=["eslint"], paths=paths, argv=extra_args, **kwargs ) @Command( "format", category="devenv", description="Format files, alternative to 'lint --fix' ", parser=setup_argument_parser, ) def format_files(command_context, paths, extra_args=[], **kwargs): linters = kwargs["linters"] formatters = VALID_FORMATTERS if conditions.is_android(command_context): formatters |= VALID_ANDROID_FORMATTERS if not linters: linters = formatters else: invalid_linters = set(linters) - formatters if invalid_linters: print( "error: One or more linters passed are not valid formatters. " "Note that only the following linters are valid formatters:" ) print("\n".join(sorted(formatters))) return 1 kwargs["linters"] = list(linters) kwargs["fix"] = True command_context._mach_context.commands.dispatch( "lint", command_context._mach_context, paths=paths, argv=extra_args, **kwargs )