summaryrefslogtreecommitdiffstats
path: root/tools/lint
diff options
context:
space:
mode:
Diffstat (limited to '')
-rw-r--r--tools/lint/android-api-lint.yml15
-rw-r--r--tools/lint/android-checkstyle.yml15
-rw-r--r--tools/lint/android-format.yml15
-rw-r--r--tools/lint/android-javadoc.yml15
-rw-r--r--tools/lint/android-lint.yml15
-rw-r--r--tools/lint/android-test.yml15
-rw-r--r--tools/lint/android/__init__.py0
-rw-r--r--tools/lint/android/lints.py417
-rw-r--r--tools/lint/black.yml18
-rw-r--r--tools/lint/clang-format.yml12
-rw-r--r--tools/lint/clang-format/__init__.py237
-rw-r--r--tools/lint/clippy.yml109
-rw-r--r--tools/lint/clippy/__init__.py261
-rw-r--r--tools/lint/codespell.yml97
-rw-r--r--tools/lint/cpp/__init__.py3
-rw-r--r--tools/lint/cpp/mingw-capitalization.py37
-rw-r--r--tools/lint/cpp/mingw-headers.txt1452
-rw-r--r--tools/lint/eslint.yml32
-rw-r--r--tools/lint/eslint/.eslintrc.js27
-rw-r--r--tools/lint/eslint/__init__.py156
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/.npmignore8
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/LICENSE363
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/README.md56
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/.eslintrc.js8
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/browser-test.js88
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/chrome-test.js59
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/mochitest-test.js57
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js348
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/require-jsdoc.js32
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/valid-jsdoc.js25
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/configs/xpcshell-test.js50
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js118
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-script.js28
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-worker.js25
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js39
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/jsm.js31
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/privileged.js805
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/process-script.js38
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/remote-page.js40
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/simpletest.js35
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/sjs.js30
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/special-powers-sandbox.js46
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/specific.js31
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/utils.js62
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/environments/xpcshell.js59
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js434
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js1015
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/index.js96
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-Date-timing.js59
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-removeChild.js66
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js145
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-observers.js118
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/consistent-if-bracing.js54
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browser-window-globals.js48
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-content-task-globals.js75
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js21
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js49
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/lazy-getter-object-name.js45
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-exported-symbols-as-used.js85
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js42
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js54
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js51
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-arbitrary-setTimeout.js62
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-compare-against-boolean-literals.js36
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cu-reportError.js135
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-define-cc-etc.js51
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-throw-cr-literal.js101
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-parameters.js144
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-removeEventListener.js66
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-run-test.js73
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-boolean-length-check.js126
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-formatValues.js89
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-addtask-only.js48
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-chromeutils-import-params.js62
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-eager-module-in-lazy-getter.js103
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-global-this.js40
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-globalThis-modification.js70
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-import-system-module-from-non-system.js36
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js95
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-lazy-imports-into-globals.js75
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-mixing-eager-and-lazy.js153
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-multiple-getters-calls.js81
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-osfile.js51
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-relative-requires.js36
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-scriptableunicodeconverter.js40
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js42
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-top-level-await.js45
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/rejects-requires-await.js47
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-cc-etc.js52
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-generateqi.js104
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-import.js78
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-default-preference-values.js50
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-includes-instead-of-indexOf.js47
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-isInstance.js155
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-ownerGlobal.js40
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-returnValue.js41
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-services.js104
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-static-import.js87
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-ci-uses.js167
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js220
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services-property.js126
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services.js59
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js36
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/lib/services.json61
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/manifest.tt10
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/package-lock.json6733
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/package.json55
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/reporters/mozilla-format.js57
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/scripts/createExports.js77
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-Date-timing.js42
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-removeChild.js46
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-listeners.js89
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-observers.js74
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/consistent-if-bracing.js40
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/globals.js161
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/lazy-getter-object-name.js50
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/mark-exported-symbols-as-used.js45
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js81
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-arbitrary-setTimeout.js41
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-compare-against-boolean-literals.js61
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-cu-reportError.js90
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-define-cc-etc.js62
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-throw-cr-literal.js62
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-parameters.js147
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-removeEventListener.js99
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-run-test.js124
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-boolean-length-check.js97
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-formatValues.js72
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-addtask-only.js56
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-chromeutils-import-params.js67
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-eager-module-in-lazy-getter.js80
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-global-this.js49
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-globalThis-modification.js90
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-import-system-module-from-non-system.js33
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-importGlobalProperties.js87
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-lazy-imports-into-globals.js50
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-mixing-eager-and-lazy.js124
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-multiple-getters-calls.js60
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-osfile.js50
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-relative-requires.js58
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-scriptableunicodeconverter.js33
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-some-requires.js49
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/reject-top-level-await.js36
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/rejects-requires-await.js32
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-cc-etc.js60
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-generateqi.js81
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-import.js70
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-default-preference-values.js41
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-includes-instead-of-indexOf.js38
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-isInstance.js130
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-ownerGlobal.js35
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-returnValue.js39
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-services.js60
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/use-static-import.js88
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/valid-ci-uses.js58
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js151
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services-property.js42
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services.js33
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/docshell.xpt6077
-rw-r--r--tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/xpcom_base.xpt3197
-rwxr-xr-xtools/lint/eslint/eslint-plugin-mozilla/update.sh61
-rw-r--r--tools/lint/eslint/eslint-plugin-spidermonkey-js/LICENSE363
-rw-r--r--tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/environments/self-hosted.js180
-rw-r--r--tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/index.js20
-rw-r--r--tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/processors/self-hosted.js128
-rw-r--r--tools/lint/eslint/eslint-plugin-spidermonkey-js/package.json30
-rw-r--r--tools/lint/eslint/manifest.tt10
-rw-r--r--tools/lint/eslint/setup_helper.py421
-rwxr-xr-xtools/lint/eslint/update.sh71
-rw-r--r--tools/lint/file-perm.yml53
-rw-r--r--tools/lint/file-perm/__init__.py45
-rw-r--r--tools/lint/file-whitespace.yml163
-rw-r--r--tools/lint/file-whitespace/__init__.py124
-rw-r--r--tools/lint/flake8.yml15
-rw-r--r--tools/lint/fluent-lint.yml10
-rw-r--r--tools/lint/fluent-lint/__init__.py381
-rw-r--r--tools/lint/fluent-lint/exclusions.yml192
-rwxr-xr-xtools/lint/hooks.py63
-rwxr-xr-xtools/lint/hooks_clang_format.py99
-rwxr-xr-xtools/lint/hooks_js_format.py83
-rw-r--r--tools/lint/isort.yml13
-rw-r--r--tools/lint/l10n.yml38
-rw-r--r--tools/lint/libpref/__init__.py112
-rw-r--r--tools/lint/license.yml94
-rw-r--r--tools/lint/license/__init__.py195
-rw-r--r--tools/lint/license/valid-licenses.txt29
-rw-r--r--tools/lint/lintpref.yml17
-rw-r--r--tools/lint/mach_commands.py189
-rw-r--r--tools/lint/mingw-capitalization.yml12
-rw-r--r--tools/lint/mscom-init.yml46
-rw-r--r--tools/lint/perfdocs.yml16
-rw-r--r--tools/lint/perfdocs/__init__.py13
-rw-r--r--tools/lint/perfdocs/doc_helpers.py85
-rw-r--r--tools/lint/perfdocs/framework_gatherers.py569
-rw-r--r--tools/lint/perfdocs/gatherer.py156
-rw-r--r--tools/lint/perfdocs/generator.py281
-rw-r--r--tools/lint/perfdocs/logger.py81
-rw-r--r--tools/lint/perfdocs/perfdocs.py95
-rw-r--r--tools/lint/perfdocs/templates/index.rst86
-rw-r--r--tools/lint/perfdocs/utils.py156
-rw-r--r--tools/lint/perfdocs/verifier.py600
-rw-r--r--tools/lint/pylint.yml24
-rw-r--r--tools/lint/python/__init__.py3
-rw-r--r--tools/lint/python/black.py179
-rw-r--r--tools/lint/python/black_requirements.in4
-rw-r--r--tools/lint/python/black_requirements.txt118
-rw-r--r--tools/lint/python/flake8.py215
-rw-r--r--tools/lint/python/flake8_requirements.in4
-rw-r--r--tools/lint/python/flake8_requirements.txt41
-rw-r--r--tools/lint/python/isort.py138
-rw-r--r--tools/lint/python/isort_requirements.in1
-rw-r--r--tools/lint/python/isort_requirements.txt10
-rw-r--r--tools/lint/python/l10n_lint.py171
-rw-r--r--tools/lint/python/pylint.py133
-rw-r--r--tools/lint/python/pylint_requirements.in5
-rw-r--r--tools/lint/python/pylint_requirements.txt136
-rw-r--r--tools/lint/rejected-words.yml469
-rw-r--r--tools/lint/rst.yml11
-rw-r--r--tools/lint/rst/__init__.py116
-rw-r--r--tools/lint/rst/requirements.in2
-rw-r--r--tools/lint/rst/requirements.txt17
-rw-r--r--tools/lint/rust/__init__.py180
-rw-r--r--tools/lint/rustfmt.yml20
-rw-r--r--tools/lint/shell/__init__.py148
-rw-r--r--tools/lint/shellcheck.yml15
-rw-r--r--tools/lint/spell/__init__.py168
-rw-r--r--tools/lint/spell/codespell_requirements.in1
-rw-r--r--tools/lint/spell/codespell_requirements.txt10
-rw-r--r--tools/lint/spell/exclude-list.txt23
-rw-r--r--tools/lint/test-manifest-disable.yml16
-rw-r--r--tools/lint/test-manifest-skip-if.yml18
-rw-r--r--tools/lint/test/conftest.py301
-rw-r--r--tools/lint/test/files/android-format/Bad.java8
-rw-r--r--tools/lint/test/files/android-format/Main.kt7
-rw-r--r--tools/lint/test/files/android-format/build.gradle1
-rw-r--r--tools/lint/test/files/black/bad.py6
-rw-r--r--tools/lint/test/files/black/invalid.py4
-rw-r--r--tools/lint/test/files/clang-format/bad/bad.cpp6
-rw-r--r--tools/lint/test/files/clang-format/bad/bad2.c8
-rw-r--r--tools/lint/test/files/clang-format/bad/bad2.h1
-rw-r--r--tools/lint/test/files/clang-format/bad/good.cpp1
-rw-r--r--tools/lint/test/files/clang-format/good/foo.cpp1
-rw-r--r--tools/lint/test/files/clippy/test1/Cargo.toml17
-rw-r--r--tools/lint/test/files/clippy/test1/bad.rs14
-rw-r--r--tools/lint/test/files/clippy/test1/bad2.rs14
-rw-r--r--tools/lint/test/files/clippy/test1/good.rs6
-rw-r--r--tools/lint/test/files/clippy/test2/Cargo.lock5
-rw-r--r--tools/lint/test/files/clippy/test2/Cargo.toml8
-rw-r--r--tools/lint/test/files/clippy/test2/src/bad_1.rs15
-rw-r--r--tools/lint/test/files/clippy/test2/src/bad_2.rs17
-rw-r--r--tools/lint/test/files/codespell/ignore.rst5
-rw-r--r--tools/lint/test/files/eslint/good.js0
-rw-r--r--tools/lint/test/files/eslint/import/bad_import.js1
-rw-r--r--tools/lint/test/files/eslint/nolint/foo.txt0
-rw-r--r--tools/lint/test/files/eslint/subdir/bad.js2
-rwxr-xr-xtools/lint/test/files/file-perm/maybe-shebang/bad.js2
-rwxr-xr-xtools/lint/test/files/file-perm/maybe-shebang/good.js5
-rwxr-xr-xtools/lint/test/files/file-perm/no-shebang/bad-shebang.c2
-rwxr-xr-xtools/lint/test/files/file-perm/no-shebang/bad.c1
-rwxr-xr-xtools/lint/test/files/file-perm/no-shebang/bad.pngbin0 -> 49 bytes
-rw-r--r--tools/lint/test/files/file-perm/no-shebang/good.c1
-rw-r--r--tools/lint/test/files/file-whitespace/bad-newline.c3
-rw-r--r--tools/lint/test/files/file-whitespace/bad-windows.c3
-rw-r--r--tools/lint/test/files/file-whitespace/bad.c3
-rw-r--r--tools/lint/test/files/file-whitespace/bad.js3
-rw-r--r--tools/lint/test/files/file-whitespace/good.c1
-rw-r--r--tools/lint/test/files/file-whitespace/good.js5
-rw-r--r--tools/lint/test/files/flake8/.flake84
-rw-r--r--tools/lint/test/files/flake8/bad.py5
-rw-r--r--tools/lint/test/files/flake8/custom/.flake84
-rw-r--r--tools/lint/test/files/flake8/custom/good.py5
-rw-r--r--tools/lint/test/files/flake8/ext/bad.configure2
-rw-r--r--tools/lint/test/files/flake8/subdir/exclude/bad.py5
-rw-r--r--tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py5
-rw-r--r--tools/lint/test/files/fluent-lint/bad.ftl44
-rw-r--r--tools/lint/test/files/fluent-lint/brand-names-excluded.ftl2
-rw-r--r--tools/lint/test/files/fluent-lint/brand-names.ftl30
-rw-r--r--tools/lint/test/files/fluent-lint/comment-group1.ftl35
-rw-r--r--tools/lint/test/files/fluent-lint/comment-group2.ftl15
-rw-r--r--tools/lint/test/files/fluent-lint/comment-resource1.ftl11
-rw-r--r--tools/lint/test/files/fluent-lint/comment-resource2.ftl6
-rw-r--r--tools/lint/test/files/fluent-lint/comment-resource3.ftl6
-rw-r--r--tools/lint/test/files/fluent-lint/comment-resource4.ftl8
-rw-r--r--tools/lint/test/files/fluent-lint/comment-resource5.ftl8
-rw-r--r--tools/lint/test/files/fluent-lint/comment-resource6.ftl4
-rw-r--r--tools/lint/test/files/fluent-lint/excluded.ftl6
-rw-r--r--tools/lint/test/files/fluent-lint/tools/lint/fluent-lint/exclusions.yml17
-rw-r--r--tools/lint/test/files/isort/.flake84
-rw-r--r--tools/lint/test/files/isort/bad.py8
-rw-r--r--tools/lint/test/files/isort/subdir/exclude/bad.py9
-rw-r--r--tools/lint/test/files/license/.eslintrc.js5
-rw-r--r--tools/lint/test/files/license/bad.c1
-rw-r--r--tools/lint/test/files/license/bad.js6
-rw-r--r--tools/lint/test/files/license/good-other.h9
-rw-r--r--tools/lint/test/files/license/good.c8
-rw-r--r--tools/lint/test/files/license/good.js7
-rw-r--r--tools/lint/test/files/lintpref/bad.js2
-rw-r--r--tools/lint/test/files/lintpref/good.js6
-rw-r--r--tools/lint/test/files/pylint/bad.py5
-rw-r--r--tools/lint/test/files/pylint/good.py3
-rw-r--r--tools/lint/test/files/rst/.dotfile.rst11
-rw-r--r--tools/lint/test/files/rst/bad.rst20
-rw-r--r--tools/lint/test/files/rst/bad2.rst4
-rw-r--r--tools/lint/test/files/rst/bad3.rst6
-rw-r--r--tools/lint/test/files/rst/good.rst11
-rw-r--r--tools/lint/test/files/rustfmt/subdir/bad.rs16
-rw-r--r--tools/lint/test/files/rustfmt/subdir/bad2.rs17
-rw-r--r--tools/lint/test/files/rustfmt/subdir/good.rs6
-rw-r--r--tools/lint/test/files/shellcheck/bad.sh3
-rw-r--r--tools/lint/test/files/shellcheck/good.sh2
-rw-r--r--tools/lint/test/files/trojan-source/README5
-rw-r--r--tools/lint/test/files/trojan-source/commenting-out.cpp9
-rw-r--r--tools/lint/test/files/trojan-source/early-return.py9
-rw-r--r--tools/lint/test/files/trojan-source/invisible-function.rs15
-rw-r--r--tools/lint/test/files/updatebot/.yamllint6
-rw-r--r--tools/lint/test/files/updatebot/cargo-mismatch.yaml44
-rw-r--r--tools/lint/test/files/updatebot/good1.yaml44
-rw-r--r--tools/lint/test/files/updatebot/good2.yaml74
-rw-r--r--tools/lint/test/files/updatebot/no-revision.yaml43
-rw-r--r--tools/lint/test/files/yaml/.yamllint6
-rw-r--r--tools/lint/test/files/yaml/bad.yml8
-rw-r--r--tools/lint/test/files/yaml/good.yml6
-rw-r--r--tools/lint/test/python.ini33
-rw-r--r--tools/lint/test/test_android_format.py38
-rw-r--r--tools/lint/test/test_black.py53
-rw-r--r--tools/lint/test/test_clang_format.py139
-rw-r--r--tools/lint/test/test_clippy.py121
-rw-r--r--tools/lint/test/test_codespell.py37
-rw-r--r--tools/lint/test/test_eslint.py74
-rw-r--r--tools/lint/test/test_file_license.py23
-rw-r--r--tools/lint/test/test_file_perm.py35
-rw-r--r--tools/lint/test/test_file_whitespace.py51
-rw-r--r--tools/lint/test/test_flake8.py117
-rw-r--r--tools/lint/test/test_fluent_lint.py134
-rw-r--r--tools/lint/test/test_isort.py114
-rw-r--r--tools/lint/test/test_lintpref.py16
-rw-r--r--tools/lint/test/test_perfdocs.py846
-rw-r--r--tools/lint/test/test_perfdocs_generation.py297
-rw-r--r--tools/lint/test/test_perfdocs_helpers.py206
-rw-r--r--tools/lint/test/test_pylint.py24
-rw-r--r--tools/lint/test/test_rst.py25
-rw-r--r--tools/lint/test/test_rustfmt.py70
-rw-r--r--tools/lint/test/test_shellcheck.py26
-rw-r--r--tools/lint/test/test_trojan_source.py25
-rw-r--r--tools/lint/test/test_updatebot.py44
-rw-r--r--tools/lint/test/test_yaml.py28
-rw-r--r--tools/lint/tox/tox_requirements.txt7
-rw-r--r--tools/lint/trojan-source.yml28
-rw-r--r--tools/lint/trojan-source/__init__.py67
-rw-r--r--tools/lint/updatebot.yml9
-rw-r--r--tools/lint/updatebot/__init__.py3
-rw-r--r--tools/lint/updatebot/validate_yaml.py52
-rw-r--r--tools/lint/wpt.yml10
-rw-r--r--tools/lint/wpt/__init__.py3
-rw-r--r--tools/lint/wpt/wpt.py59
-rw-r--r--tools/lint/yaml.yml18
-rw-r--r--tools/lint/yamllint_/__init__.py101
357 files changed, 42650 insertions, 0 deletions
diff --git a/tools/lint/android-api-lint.yml b/tools/lint/android-api-lint.yml
new file mode 100644
index 0000000000..0b81e79a83
--- /dev/null
+++ b/tools/lint/android-api-lint.yml
@@ -0,0 +1,15 @@
+---
+android-api-lint:
+ description: Android api-lint
+ include: ['mobile/android']
+ exclude: []
+ extensions: ['java', 'kt']
+ support-files:
+ - 'mobile/android/**/Makefile.in'
+ - 'mobile/android/config/**'
+ - 'mobile/android/gradle.configure'
+ - 'mobile/android/**/moz.build'
+ - '**/*.gradle'
+ type: global
+ payload: android.lints:api_lint
+ setup: android.lints:setup
diff --git a/tools/lint/android-checkstyle.yml b/tools/lint/android-checkstyle.yml
new file mode 100644
index 0000000000..abd4974e6a
--- /dev/null
+++ b/tools/lint/android-checkstyle.yml
@@ -0,0 +1,15 @@
+---
+android-checkstyle:
+ description: Android checkstyle
+ include: ['mobile/android']
+ exclude: []
+ extensions: ['java', 'kt']
+ support-files:
+ - 'mobile/android/**/Makefile.in'
+ - 'mobile/android/config/**'
+ - 'mobile/android/gradle.configure'
+ - 'mobile/android/**/moz.build'
+ - '**/*.gradle'
+ type: global
+ payload: android.lints:checkstyle
+ setup: android.lints:setup
diff --git a/tools/lint/android-format.yml b/tools/lint/android-format.yml
new file mode 100644
index 0000000000..cacf3ff2d7
--- /dev/null
+++ b/tools/lint/android-format.yml
@@ -0,0 +1,15 @@
+---
+android-format:
+ description: Android formatting lint
+ include: ['mobile/android']
+ exclude: []
+ extensions: ['java', 'kt']
+ support-files:
+ - 'mobile/android/**/Makefile.in'
+ - 'mobile/android/config/**'
+ - 'mobile/android/gradle.configure'
+ - 'mobile/android/**/moz.build'
+ - '**/*.gradle'
+ type: global
+ payload: android.lints:format
+ setup: android.lints:setup
diff --git a/tools/lint/android-javadoc.yml b/tools/lint/android-javadoc.yml
new file mode 100644
index 0000000000..a0811a08cd
--- /dev/null
+++ b/tools/lint/android-javadoc.yml
@@ -0,0 +1,15 @@
+---
+android-javadoc:
+ description: Android javadoc
+ include: ['mobile/android/geckoview']
+ exclude: []
+ extensions: ['java', 'kt']
+ support-files:
+ - 'mobile/android/**/Makefile.in'
+ - 'mobile/android/config/**'
+ - 'mobile/android/gradle.configure'
+ - 'mobile/android/**/moz.build'
+ - '**/*.gradle'
+ type: global
+ payload: android.lints:javadoc
+ setup: android.lints:setup
diff --git a/tools/lint/android-lint.yml b/tools/lint/android-lint.yml
new file mode 100644
index 0000000000..6a1dbe7b74
--- /dev/null
+++ b/tools/lint/android-lint.yml
@@ -0,0 +1,15 @@
+---
+android-lint:
+ description: Android lint
+ include: ['mobile/android']
+ exclude: []
+ extensions: ['java', 'kt']
+ support-files:
+ - 'mobile/android/**/Makefile.in'
+ - 'mobile/android/config/**'
+ - 'mobile/android/gradle.configure'
+ - 'mobile/android/**/moz.build'
+ - '**/*.gradle'
+ type: global
+ payload: android.lints:lint
+ setup: android.lints:setup
diff --git a/tools/lint/android-test.yml b/tools/lint/android-test.yml
new file mode 100644
index 0000000000..65739295dd
--- /dev/null
+++ b/tools/lint/android-test.yml
@@ -0,0 +1,15 @@
+---
+android-test:
+ description: Android test
+ include: ['mobile/android']
+ exclude: []
+ extensions: ['java', 'kt']
+ support-files:
+ - 'mobile/android/**/Makefile.in'
+ - 'mobile/android/config/**'
+ - 'mobile/android/gradle.configure'
+ - 'mobile/android/**/moz.build'
+ - '**/*.gradle'
+ type: global
+ payload: android.lints:test
+ setup: android.lints:setup
diff --git a/tools/lint/android/__init__.py b/tools/lint/android/__init__.py
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tools/lint/android/__init__.py
diff --git a/tools/lint/android/lints.py b/tools/lint/android/lints.py
new file mode 100644
index 0000000000..132ec3365e
--- /dev/null
+++ b/tools/lint/android/lints.py
@@ -0,0 +1,417 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 glob
+import itertools
+import json
+import os
+import re
+import subprocess
+import sys
+import xml.etree.ElementTree as ET
+
+import mozpack.path as mozpath
+import six
+from mozlint import result
+from mozpack.files import FileFinder
+
+# The Gradle target invocations are serialized with a simple locking file scheme. It's fine for
+# them to take a while, since the first will compile all the Java, etc, and then perform
+# potentially expensive static analyses.
+GRADLE_LOCK_MAX_WAIT_SECONDS = 20 * 60
+
+
+def setup(root, **setupargs):
+ if setupargs.get("substs", {}).get("MOZ_BUILD_APP") != "mobile/android":
+ return 1
+
+ if "topobjdir" not in setupargs:
+ print(
+ "Skipping {}: a configured Android build is required!".format(
+ setupargs["name"]
+ )
+ )
+ return 1
+
+ return 0
+
+
+def gradle(log, topsrcdir=None, topobjdir=None, tasks=[], extra_args=[], verbose=True):
+ sys.path.insert(0, os.path.join(topsrcdir, "mobile", "android"))
+ from gradle import gradle_lock
+
+ with gradle_lock(topobjdir, max_wait_seconds=GRADLE_LOCK_MAX_WAIT_SECONDS):
+ # The android-lint parameter can be used by gradle tasks to run special
+ # logic when they are run for a lint using
+ # project.hasProperty('android-lint')
+ cmd_args = (
+ [
+ sys.executable,
+ os.path.join(topsrcdir, "mach"),
+ "gradle",
+ "--verbose",
+ "-Pandroid-lint",
+ "--",
+ ]
+ + tasks
+ + extra_args
+ )
+
+ cmd = " ".join(six.moves.shlex_quote(arg) for arg in cmd_args)
+ log.debug(cmd)
+
+ # Gradle and mozprocess do not get along well, so we use subprocess
+ # directly.
+ proc = subprocess.Popen(cmd_args, cwd=topsrcdir)
+ status = None
+ # Leave it to the subprocess to handle Ctrl+C. If it terminates as a result
+ # of Ctrl+C, proc.wait() will return a status code, and, we get out of the
+ # loop. If it doesn't, like e.g. gdb, we continue waiting.
+ while status is None:
+ try:
+ status = proc.wait()
+ except KeyboardInterrupt:
+ pass
+
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+ raise
+
+ return proc.returncode
+
+
+def format(config, fix=None, **lintargs):
+ topsrcdir = lintargs["root"]
+ topobjdir = lintargs["topobjdir"]
+
+ if fix:
+ tasks = lintargs["substs"]["GRADLE_ANDROID_FORMAT_LINT_FIX_TASKS"]
+ else:
+ tasks = lintargs["substs"]["GRADLE_ANDROID_FORMAT_LINT_CHECK_TASKS"]
+
+ ret = gradle(
+ lintargs["log"],
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ tasks=tasks,
+ extra_args=lintargs.get("extra_args") or [],
+ )
+
+ results = []
+ for path in lintargs["substs"]["GRADLE_ANDROID_FORMAT_LINT_FOLDERS"]:
+ folder = os.path.join(
+ topobjdir, "gradle", "build", path, "spotless", "spotlessJava"
+ )
+ for filename in glob.iglob(folder + "/**/*.java", recursive=True):
+ err = {
+ "rule": "spotless-java",
+ "path": os.path.join(path, mozpath.relpath(filename, folder)),
+ "lineno": 0,
+ "column": 0,
+ "message": "Formatting error, please run ./mach lint -l android-format --fix",
+ "level": "error",
+ }
+ results.append(result.from_config(config, **err))
+ folder = os.path.join(
+ topobjdir, "gradle", "build", path, "spotless", "spotlessKotlin"
+ )
+ for filename in glob.iglob(folder + "/**/*.kt", recursive=True):
+ err = {
+ "rule": "spotless-kt",
+ "path": os.path.join(path, mozpath.relpath(filename, folder)),
+ "lineno": 0,
+ "column": 0,
+ "message": "Formatting error, please run ./mach lint -l android-format --fix",
+ "level": "error",
+ }
+ results.append(result.from_config(config, **err))
+
+ if len(results) == 0 and ret != 0:
+ # spotless seems to hit unfixed error.
+ err = {
+ "rule": "spotless",
+ "path": "",
+ "lineno": 0,
+ "column": 0,
+ "message": "Unexpected error",
+ "level": "error",
+ }
+ results.append(result.from_config(config, **err))
+
+ # If --fix was passed, we just report the number of files that were changed
+ if fix:
+ return {"results": [], "fixed": len(results)}
+ return results
+
+
+def api_lint(config, **lintargs):
+ topsrcdir = lintargs["root"]
+ topobjdir = lintargs["topobjdir"]
+
+ gradle(
+ lintargs["log"],
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ tasks=lintargs["substs"]["GRADLE_ANDROID_API_LINT_TASKS"],
+ extra_args=lintargs.get("extra_args") or [],
+ )
+
+ folder = lintargs["substs"]["GRADLE_ANDROID_GECKOVIEW_APILINT_FOLDER"]
+
+ results = []
+
+ with open(os.path.join(topobjdir, folder, "apilint-result.json")) as f:
+ issues = json.load(f)
+
+ for rule in ("compat_failures", "failures"):
+ for r in issues[rule]:
+ err = {
+ "rule": r["rule"] if rule == "failures" else "compat_failures",
+ "path": mozpath.relpath(r["file"], topsrcdir),
+ "lineno": int(r["line"]),
+ "column": int(r.get("column") or 0),
+ "message": r["msg"],
+ "level": "error" if r["error"] else "warning",
+ }
+ results.append(result.from_config(config, **err))
+
+ for r in issues["api_changes"]:
+ err = {
+ "rule": "api_changes",
+ "path": mozpath.relpath(r["file"], topsrcdir),
+ "lineno": int(r["line"]),
+ "column": int(r.get("column") or 0),
+ "message": "Unexpected api change. Please run ./mach gradle {} for more "
+ "information".format(
+ " ".join(lintargs["substs"]["GRADLE_ANDROID_API_LINT_TASKS"])
+ ),
+ }
+ results.append(result.from_config(config, **err))
+
+ return results
+
+
+def javadoc(config, **lintargs):
+ topsrcdir = lintargs["root"]
+ topobjdir = lintargs["topobjdir"]
+
+ gradle(
+ lintargs["log"],
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ tasks=lintargs["substs"]["GRADLE_ANDROID_GECKOVIEW_DOCS_TASKS"],
+ extra_args=lintargs.get("extra_args") or [],
+ )
+
+ output_files = lintargs["substs"]["GRADLE_ANDROID_GECKOVIEW_DOCS_OUTPUT_FILES"]
+
+ results = []
+
+ for output_file in output_files:
+ with open(os.path.join(topobjdir, output_file)) as f:
+ # Like: '[{"path":"/absolute/path/to/topsrcdir/mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlocking.java","lineno":"462","level":"warning","message":"no @return"}]'. # NOQA: E501
+ issues = json.load(f)
+
+ for issue in issues:
+ issue["path"] = issue["path"].replace(lintargs["root"], "")
+ # We want warnings to be errors for linting purposes.
+ # TODO: Bug 1316188 - resolve missing javadoc comments
+ issue["level"] = (
+ "error" if issue["message"] != ": no comment" else "warning"
+ )
+ results.append(result.from_config(config, **issue))
+
+ return results
+
+
+def lint(config, **lintargs):
+ topsrcdir = lintargs["root"]
+ topobjdir = lintargs["topobjdir"]
+
+ gradle(
+ lintargs["log"],
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ tasks=lintargs["substs"]["GRADLE_ANDROID_LINT_TASKS"],
+ extra_args=lintargs.get("extra_args") or [],
+ )
+
+ # It's surprising that this is the App variant name, but this is "withoutGeckoBinariesDebug"
+ # right now and the GeckoView variant name is "withGeckoBinariesDebug". This will be addressed
+ # as we unify variants.
+ path = os.path.join(
+ lintargs["topobjdir"],
+ "gradle/build/mobile/android/geckoview/reports",
+ "lint-results-{}.xml".format(
+ lintargs["substs"]["GRADLE_ANDROID_GECKOVIEW_VARIANT_NAME"]
+ ),
+ )
+ tree = ET.parse(open(path, "rt"))
+ root = tree.getroot()
+
+ results = []
+
+ for issue in root.findall("issue"):
+ location = issue[0]
+ if "third_party" in location.get("file") or "thirdparty" in location.get(
+ "file"
+ ):
+ continue
+ err = {
+ "level": issue.get("severity").lower(),
+ "rule": issue.get("id"),
+ "message": issue.get("message"),
+ "path": location.get("file").replace(lintargs["root"], ""),
+ "lineno": int(location.get("line") or 0),
+ }
+ results.append(result.from_config(config, **err))
+
+ return results
+
+
+def _parse_checkstyle_output(config, topsrcdir=None, report_path=None):
+ tree = ET.parse(open(report_path, "rt"))
+ root = tree.getroot()
+
+ for file in root.findall("file"):
+ sourcepath = file.get("name").replace(topsrcdir + "/", "")
+
+ for error in file.findall("error"):
+ # Like <error column="42" line="22" message="Name 'mPorts' must match pattern 'xm[A-Z][A-Za-z]*$'." severity="error" source="com.puppycrawl.tools.checkstyle.checks.naming.MemberNameCheck" />. # NOQA: E501
+ err = {
+ "level": "error",
+ "rule": error.get("source"),
+ "message": error.get("message"),
+ "path": sourcepath,
+ "lineno": int(error.get("line") or 0),
+ "column": int(error.get("column") or 0),
+ }
+ yield result.from_config(config, **err)
+
+
+def checkstyle(config, **lintargs):
+ topsrcdir = lintargs["root"]
+ topobjdir = lintargs["topobjdir"]
+
+ gradle(
+ lintargs["log"],
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ tasks=lintargs["substs"]["GRADLE_ANDROID_CHECKSTYLE_TASKS"],
+ extra_args=lintargs.get("extra_args") or [],
+ )
+
+ results = []
+
+ for relative_path in lintargs["substs"]["GRADLE_ANDROID_CHECKSTYLE_OUTPUT_FILES"]:
+ report_path = os.path.join(lintargs["topobjdir"], relative_path)
+ results.extend(
+ _parse_checkstyle_output(
+ config, topsrcdir=lintargs["root"], report_path=report_path
+ )
+ )
+
+ return results
+
+
+def _parse_android_test_results(config, topsrcdir=None, report_dir=None):
+ # A brute force way to turn a Java FQN into a path on disk. Assumes Java
+ # and Kotlin sources are in mobile/android for performance and simplicity.
+ sourcepath_finder = FileFinder(os.path.join(topsrcdir, "mobile", "android"))
+
+ finder = FileFinder(report_dir)
+ reports = list(finder.find("TEST-*.xml"))
+ if not reports:
+ raise RuntimeError("No reports found under {}".format(report_dir))
+
+ for report, _ in reports:
+ tree = ET.parse(open(os.path.join(finder.base, report), "rt"))
+ root = tree.getroot()
+
+ class_name = root.get(
+ "name"
+ ) # Like 'org.mozilla.gecko.permissions.TestPermissions'.
+ path = (
+ "**/" + class_name.replace(".", "/") + ".*"
+ ) # Like '**/org/mozilla/gecko/permissions/TestPermissions.*'. # NOQA: E501
+
+ for testcase in root.findall("testcase"):
+ function_name = testcase.get("name")
+
+ # Schema cribbed from http://llg.cubic.org/docs/junit/.
+ for unexpected in itertools.chain(
+ testcase.findall("error"), testcase.findall("failure")
+ ):
+ sourcepaths = list(sourcepath_finder.find(path))
+ if not sourcepaths:
+ raise RuntimeError(
+ "No sourcepath found for class {class_name}".format(
+ class_name=class_name
+ )
+ )
+
+ for sourcepath, _ in sourcepaths:
+ lineno = 0
+ message = unexpected.get("message")
+ # Turn '... at org.mozilla.gecko.permissions.TestPermissions.testMultipleRequestsAreQueuedAndDispatchedSequentially(TestPermissions.java:118)' into 118. # NOQA: E501
+ pattern = r"at {class_name}\.{function_name}\(.*:(\d+)\)"
+ pattern = pattern.format(
+ class_name=class_name, function_name=function_name
+ )
+ match = re.search(pattern, message)
+ if match:
+ lineno = int(match.group(1))
+ else:
+ msg = "No source line found for {class_name}.{function_name}".format(
+ class_name=class_name, function_name=function_name
+ )
+ raise RuntimeError(msg)
+
+ err = {
+ "level": "error",
+ "rule": unexpected.get("type"),
+ "message": message,
+ "path": os.path.join("mobile", "android", sourcepath),
+ "lineno": lineno,
+ }
+ yield result.from_config(config, **err)
+
+
+def test(config, **lintargs):
+ topsrcdir = lintargs["root"]
+ topobjdir = lintargs["topobjdir"]
+
+ gradle(
+ lintargs["log"],
+ topsrcdir=topsrcdir,
+ topobjdir=topobjdir,
+ tasks=lintargs["substs"]["GRADLE_ANDROID_TEST_TASKS"],
+ extra_args=lintargs.get("extra_args") or [],
+ )
+
+ results = []
+
+ def capitalize(s):
+ # Can't use str.capitalize because it lower cases trailing letters.
+ return (s[0].upper() + s[1:]) if s else ""
+
+ pairs = [("geckoview", lintargs["substs"]["GRADLE_ANDROID_GECKOVIEW_VARIANT_NAME"])]
+ for project, variant in pairs:
+ report_dir = os.path.join(
+ lintargs["topobjdir"],
+ "gradle/build/mobile/android/{}/test-results/test{}UnitTest".format(
+ project, capitalize(variant)
+ ),
+ )
+ results.extend(
+ _parse_android_test_results(
+ config, topsrcdir=lintargs["root"], report_dir=report_dir
+ )
+ )
+
+ return results
diff --git a/tools/lint/black.yml b/tools/lint/black.yml
new file mode 100644
index 0000000000..4c0f8533cc
--- /dev/null
+++ b/tools/lint/black.yml
@@ -0,0 +1,18 @@
+---
+black:
+ description: Reformat python
+ exclude:
+ - gfx/harfbuzz/src/meson.build
+ - '**/*.mako.py'
+ - python/mozbuild/mozbuild/test/frontend/data/reader-error-syntax/moz.build
+ - testing/mozharness/configs/test/test_malformed.py
+ - testing/web-platform/tests
+ extensions:
+ - build
+ - configure
+ - py
+ support-files:
+ - 'tools/lint/python/**'
+ type: external
+ payload: python.black:lint
+ setup: python.black:setup
diff --git a/tools/lint/clang-format.yml b/tools/lint/clang-format.yml
new file mode 100644
index 0000000000..bcdf9d8591
--- /dev/null
+++ b/tools/lint/clang-format.yml
@@ -0,0 +1,12 @@
+---
+clang-format:
+ description: Reformat C/C++
+ include:
+ - '.'
+ extensions: ['cpp', 'c', 'cc', 'h', 'm', 'mm']
+ support-files:
+ - 'tools/lint/clang-format/**'
+ type: external
+ payload: clang-format:lint
+ code_review_warnings: false
+ setup: clang-format:setup
diff --git a/tools/lint/clang-format/__init__.py b/tools/lint/clang-format/__init__.py
new file mode 100644
index 0000000000..d0a81f7b06
--- /dev/null
+++ b/tools/lint/clang-format/__init__.py
@@ -0,0 +1,237 @@
+# 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 re
+import signal
+import sys
+import xml.etree.ElementTree as ET
+
+from mozboot.util import get_tools_dir
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+from mozprocess import ProcessHandler
+
+CLANG_FORMAT_NOT_FOUND = """
+Could not find clang-format! It should've been installed automatically - \
+please report a bug here:
+https://bugzilla.mozilla.org/enter_bug.cgi?product=Firefox%20Build%20System&component=Lint%20and%20Formatting
+""".strip()
+
+
+def setup(root, mach_command_context, **lintargs):
+ if get_clang_format_binary():
+ return 0
+
+ from mozbuild.code_analysis.mach_commands import get_clang_tools
+
+ rc, _ = get_clang_tools(mach_command_context)
+ if rc:
+ return 1
+
+
+class ClangFormatProcess(ProcessHandler):
+ def __init__(self, config, *args, **kwargs):
+ self.config = config
+ kwargs["stream"] = False
+ kwargs["universal_newlines"] = True
+ ProcessHandler.__init__(self, *args, **kwargs)
+
+ def run(self, *args, **kwargs):
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ ProcessHandler.run(self, *args, **kwargs)
+ signal.signal(signal.SIGINT, orig)
+
+
+def run_process(config, cmd):
+ proc = ClangFormatProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+ return proc.output
+
+
+def get_clang_format_binary():
+ """
+ Returns the path of the first clang-format binary available
+ if not found returns None
+ """
+ binary = os.environ.get("CLANG_FORMAT")
+ if binary:
+ return binary
+
+ clang_tools_path = os.path.join(get_tools_dir(), "clang-tools")
+ bin_path = os.path.join(clang_tools_path, "clang-tidy", "bin")
+ binary = os.path.join(bin_path, "clang-format")
+
+ if sys.platform.startswith("win"):
+ binary += ".exe"
+
+ if not os.path.isfile(binary):
+ return None
+
+ return binary
+
+
+def is_ignored_path(ignored_dir_re, topsrcdir, f):
+ # Remove up to topsrcdir in pathname and match
+ if f.startswith(topsrcdir + "/"):
+ match_f = f[len(topsrcdir + "/") :]
+ else:
+ match_f = f
+ return re.match(ignored_dir_re, match_f)
+
+
+def remove_ignored_path(paths, topsrcdir, log):
+ path_to_third_party = os.path.join(topsrcdir, ".clang-format-ignore")
+
+ ignored_dir = []
+ with open(path_to_third_party, "r") as fh:
+ for line in fh:
+ # In case it starts with a space
+ line = line.strip()
+ # Remove comments and empty lines
+ if line.startswith("#") or len(line) == 0:
+ continue
+ # The regexp is to make sure we are managing relative paths
+ ignored_dir.append(r"^[\./]*" + line.rstrip())
+
+ # Generates the list of regexp
+ ignored_dir_re = "(%s)" % "|".join(ignored_dir)
+
+ path_list = []
+ for f in paths:
+ if is_ignored_path(ignored_dir_re, topsrcdir, f):
+ # Early exit if we have provided an ignored directory
+ log.debug("Ignored third party code '{0}'".format(f))
+ continue
+ path_list.append(f)
+
+ return path_list
+
+
+def lint(paths, config, fix=None, **lintargs):
+ log = lintargs["log"]
+ paths = list(expand_exclusions(paths, config, lintargs["root"]))
+
+ # We ignored some specific files for a bunch of reasons.
+ # Not using excluding to avoid duplication
+ if lintargs.get("use_filters", True):
+ paths = remove_ignored_path(paths, lintargs["root"], log)
+
+ # An empty path array can occur when the user passes in `-n`. If we don't
+ # return early in this case, rustfmt will attempt to read stdin and hang.
+ if not paths:
+ return []
+
+ binary = get_clang_format_binary()
+
+ if not binary:
+ print(CLANG_FORMAT_NOT_FOUND)
+ if "MOZ_AUTOMATION" in os.environ:
+ return 1
+ return []
+
+ cmd_args = [binary]
+
+ base_command = cmd_args + ["--version"]
+ version = run_process(config, base_command)
+ log.debug("Version: {}".format(version))
+
+ cmd_args.append("--output-replacements-xml")
+ base_command = cmd_args + paths
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ output = run_process(config, base_command)
+
+ def replacement(parser):
+ for end, e in parser.read_events():
+ assert end == "end"
+ if e.tag == "replacement":
+ item = {k: int(v) for k, v in e.items()}
+ assert sorted(item.keys()) == ["length", "offset"]
+ item["with"] = (e.text or "").encode("utf-8")
+ yield item
+
+ # When given multiple paths as input, --output-replacements-xml
+ # will output one xml per path, in the order they are given, but
+ # XML parsers don't know how to handle that, so do it manually.
+ parser = None
+ replacements = []
+ for line in output:
+ if line.startswith("<?xml "):
+ if parser:
+ replacements.append(list(replacement(parser)))
+ parser = ET.XMLPullParser(["end"])
+ parser.feed(line)
+ replacements.append(list(replacement(parser)))
+
+ results = []
+ fixed = 0
+ for path, replacement in zip(paths, replacements):
+ if not replacement:
+ continue
+ with open(path, "rb") as fh:
+ data = fh.read()
+
+ linenos = []
+ patched_data = b""
+ last_offset = 0
+ lineno_before = 1
+ lineno_after = 1
+
+ for item in replacement:
+ offset = item["offset"]
+ length = item["length"]
+ replace_with = item["with"]
+ since_last_offset = data[last_offset:offset]
+ replaced = data[offset : offset + length]
+
+ lines_since_last_offset = since_last_offset.count(b"\n")
+ lineno_before += lines_since_last_offset
+ lineno_after += lines_since_last_offset
+ start_lineno = (lineno_before, lineno_after)
+
+ lineno_before += replaced.count(b"\n")
+ lineno_after += replace_with.count(b"\n")
+ end_lineno = (lineno_before, lineno_after)
+
+ if linenos and start_lineno[0] <= linenos[-1][1][0]:
+ linenos[-1] = (linenos[-1][0], end_lineno)
+ else:
+ linenos.append((start_lineno, end_lineno))
+
+ patched_data += since_last_offset + replace_with
+ last_offset = offset + len(replaced)
+ patched_data += data[last_offset:]
+
+ lines_before = data.decode("utf-8", "replace").splitlines()
+ lines_after = patched_data.decode("utf-8", "replace").splitlines()
+ for (start_before, start_after), (end_before, end_after) in linenos:
+ diff = "".join(
+ "-" + l + "\n" for l in lines_before[start_before - 1 : end_before]
+ )
+ diff += "".join(
+ "+" + l + "\n" for l in lines_after[start_after - 1 : end_after]
+ )
+
+ results.append(
+ result.from_config(
+ config,
+ path=path,
+ diff=diff,
+ level="warning",
+ lineno=start_before,
+ column=0,
+ )
+ )
+
+ if fix:
+ with open(path, "wb") as fh:
+ fh.write(patched_data)
+ fixed += len(linenos)
+
+ return {"results": results, "fixed": fixed}
diff --git a/tools/lint/clippy.yml b/tools/lint/clippy.yml
new file mode 100644
index 0000000000..b7780ba318
--- /dev/null
+++ b/tools/lint/clippy.yml
@@ -0,0 +1,109 @@
+---
+clippy:
+ description: Lint rust
+ include:
+ - build/workspace-hack/
+ - dom/midi/midir_impl/
+ - dom/media/gtest/
+ - dom/webauthn/libudev-sys/
+ - gfx/webrender_bindings/
+ - gfx/wr/peek-poke/
+ - gfx/wr/peek-poke/peek-poke-derive/
+ - gfx/wr/webrender_build/
+ - gfx/wr/wr_malloc_size_of/
+ - js/src/frontend/smoosh/
+ - js/src/rust/shared/
+ - modules/libpref/init/static_prefs/
+ - mozglue/static/rust/
+ - netwerk/base/mozurl/
+ - servo/components/derive_common/
+ - servo/components/selectors/
+ - servo/components/servo_arc/
+ - servo/components/style/
+ - servo/components/style_derive/
+ - servo/components/style_traits/
+ - servo/components/to_shmem/
+ - servo/components/to_shmem_derive/
+ - servo/tests/unit/style/
+ - testing/geckodriver/
+ - testing/mozbase/rust/mozdevice/
+ - testing/mozbase/rust/mozprofile/
+ - testing/mozbase/rust/mozrunner/
+ - testing/mozbase/rust/mozversion/
+ - testing/webdriver/
+ - third_party/rust/mp4parse/
+ - third_party/rust/mp4parse_capi/
+ - toolkit/components/kvstore/
+ - toolkit/components/glean/
+ - toolkit/components/xulstore/tests/gtest/
+ - toolkit/library/rust/
+ - tools/fuzzing/rust/
+ - tools/profiler/rust-api/
+ - xpcom/rust/gtest/bench-collections/
+ - xpcom/rust/xpcom/xpcom_macros/
+ exclude:
+ # Many are failing for the same reasons:
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1606073
+ # https://bugzilla.mozilla.org/show_bug.cgi?id=1606077
+ - Cargo.toml
+ # nsstring
+ # derive_hash_xor_eq
+ - gfx/wr/
+ - gfx/wr/webrender/
+ - gfx/wr/examples/
+ # windows-only
+ - gfx/wr/example-compositor/compositor-windows/
+ - gfx/wr/webrender_api/
+ - gfx/wr/wrench/
+ - gfx/wgpu_bindings/
+ # not_unsafe_ptr_arg_deref
+ - modules/libpref/parser/
+ - tools/profiler/rust-helper/
+ - toolkit/library/rust/shared/
+ - toolkit/library/gtest/rust/
+ # not_unsafe_ptr_arg_deref
+ - remote/
+ - dom/media/webrtc/sdp/rsdparsa_capi/
+ - intl/encoding_glue/
+ # not_unsafe_ptr_arg_deref
+ - storage/rust/
+ - storage/variant/
+ # nsstring
+ - toolkit/components/xulstore/
+ - servo/ports/geckolib/tests/
+ - xpcom/rust/xpcom/
+ - xpcom/rust/nsstring/
+ - xpcom/rust/gtest/xpcom/
+ - xpcom/rust/gtest/nsstring/
+ - security/manager/ssl/cert_storage/
+ - intl/locale/rust/fluent-langneg-ffi/
+ - intl/locale/rust/unic-langid-ffi/
+ - toolkit/components/places/bookmark_sync/
+ - xpcom/rust/nserror/
+ - xpcom/rust/moz_task/
+ - xpcom/rust/gkrust_utils/
+ - netwerk/socket/neqo_glue/
+ - dom/media/webrtc/transport/mdns_service/
+ - tools/lint/test/files/clippy/
+ - servo/ports/geckolib/
+ - servo/ports/geckolib/tests/
+ - servo/tests/unit/malloc_size_of/
+ - servo/components/malloc_size_of/
+ - dom/media/webrtc/sdp/rsdparsa_capi/
+ - testing/geckodriver/marionette/
+ - toolkit/components/bitsdownload/bits_client/
+ - gfx/wr/example-compositor/compositor/
+ - toolkit/components/bitsdownload/bits_client/bits/
+ # mac and windows only
+ - security/manager/ssl/osclientcerts/
+ extensions:
+ - rs
+ support-files:
+ - 'tools/lint/clippy/**'
+ # the version of cargo-clippy is:
+ # clippy 0.1.65 (2019147 2022-09-19)
+ # we use the date instead to facilitate the check
+ # replacing - by . because Python StrictVersion expects this
+ min_clippy_version: 2022.09.19
+ type: external
+ payload: clippy:lint
diff --git a/tools/lint/clippy/__init__.py b/tools/lint/clippy/__init__.py
new file mode 100644
index 0000000000..b972954ff4
--- /dev/null
+++ b/tools/lint/clippy/__init__.py
@@ -0,0 +1,261 @@
+# 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 json
+import os
+import re
+import signal
+
+import six
+from mozboot.util import get_tools_dir
+from mozfile import which
+from mozlint import result
+from mozlint.pathutils import get_ancestors_by_name
+from mozprocess import ProcessHandler
+from packaging.version import Version
+
+CLIPPY_WRONG_VERSION = """
+Clippy is not installed or an older version was detected. Please make sure
+clippy is installed and up to date. The minimum required version is {version}.
+
+To install:
+
+ $ rustup component add clippy
+
+To update:
+
+ $ rustup update
+
+Also ensure 'cargo' is on your $PATH.
+""".strip()
+
+
+CARGO_NOT_FOUND = """
+Could not find cargo! Install cargo.
+
+And make sure that it is in the PATH
+""".strip()
+
+
+def parse_issues(log, config, issues, base_path, onlyIn):
+ results = []
+ if onlyIn:
+ onlyIn = os.path.normcase(os.path.normpath(onlyIn))
+ for issue in issues:
+
+ try:
+ detail = json.loads(six.ensure_text(issue))
+ if "message" in detail:
+ p = detail["target"]["src_path"]
+ detail = detail["message"]
+ if "level" in detail:
+ if (
+ detail["level"] == "error" or detail["level"] == "failure-note"
+ ) and not detail["code"]:
+ log.debug(
+ "Error outside of clippy."
+ "This means that the build failed. Therefore, skipping this"
+ )
+ log.debug("File = {} / Detail = {}".format(p, detail))
+ continue
+ # We are in a clippy warning
+ if len(detail["spans"]) == 0:
+ # For some reason, at the end of the summary, we can
+ # get the following line
+ # {'rendered': 'warning: 5 warnings emitted\n\n', 'children':
+ # [], 'code': None, 'level': 'warning', 'message':
+ # '5 warnings emitted', 'spans': []}
+ # if this is the case, skip it
+ log.debug(
+ "Skipping the summary line {} for file {}".format(detail, p)
+ )
+ continue
+
+ l = detail["spans"][0]
+ p = os.path.join(base_path, l["file_name"])
+ if onlyIn and onlyIn not in os.path.normcase(os.path.normpath(p)):
+ # Case when we have a .rs in the include list in the yaml file
+ log.debug(
+ "{} is not part of the list of files '{}'".format(p, onlyIn)
+ )
+ continue
+ res = {
+ "path": p,
+ "level": detail["level"],
+ "lineno": l["line_start"],
+ "column": l["column_start"],
+ "message": detail["message"],
+ "hint": detail["rendered"],
+ "rule": detail["code"]["code"],
+ "lineoffset": l["line_end"] - l["line_start"],
+ }
+ results.append(result.from_config(config, **res))
+
+ except json.decoder.JSONDecodeError:
+ log.debug("Could not parse the output:")
+ log.debug("clippy output: {}".format(issue))
+ continue
+
+ return results
+
+
+def get_cargo_binary(log):
+ """
+ Returns the path of the first rustfmt binary available
+ if not found returns None
+ """
+ cargo_home = os.environ.get("CARGO_HOME")
+ if cargo_home:
+ log.debug("Found CARGO_HOME in {}".format(cargo_home))
+ cargo_bin = os.path.join(cargo_home, "bin", "cargo")
+ if os.path.exists(cargo_bin):
+ return cargo_bin
+ log.debug("Did not find {} in CARGO_HOME".format(cargo_bin))
+ return None
+
+ rust_path = os.path.join(get_tools_dir(), "rustc", "bin")
+ return which("cargo", path=os.pathsep.join([rust_path, os.environ["PATH"]]))
+
+
+def get_clippy_version(log, cargo):
+ """
+ Check if we are running the deprecated clippy
+ """
+ output = run_cargo_command(
+ log, [cargo, "clippy", "--version"], universal_newlines=True
+ )
+
+ version = re.findall(r"(\d+-\d+-\d+)", output[0])
+ if not version:
+ return False
+ version = Version(version[0].replace("-", "."))
+ log.debug("Found version: {}".format(version))
+ return version
+
+
+class clippyProcess(ProcessHandler):
+ def __init__(self, *args, **kwargs):
+ kwargs["stream"] = False
+ ProcessHandler.__init__(self, *args, **kwargs)
+
+ def run(self, *args, **kwargs):
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ ProcessHandler.run(self, *args, **kwargs)
+ signal.signal(signal.SIGINT, orig)
+
+
+def run_cargo_command(log, cmd, **kwargs):
+ log.debug("Command: {}".format(cmd))
+ env = os.environ.copy()
+ # Cargo doesn't find cargo-clippy on its own if it's not in `$PATH` when
+ # `$CARGO_HOME` is not set and cargo is not in `~/.cargo`.
+ env["PATH"] = os.pathsep.join([os.path.dirname(cmd[0]), os.environ["PATH"]])
+ proc = clippyProcess(cmd, env=env, **kwargs)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+ return proc.output
+
+
+def lint(paths, config, fix=None, **lintargs):
+ log = lintargs["log"]
+ cargo = get_cargo_binary(log)
+
+ if not cargo:
+ print(CARGO_NOT_FOUND)
+ if "MOZ_AUTOMATION" in os.environ:
+ return 1
+ return []
+
+ min_version_str = config.get("min_clippy_version")
+ min_version = Version(min_version_str)
+ actual_version = get_clippy_version(log, cargo)
+ log.debug(
+ "Found version: {}. Minimum expected version: {}".format(
+ actual_version, min_version
+ )
+ )
+
+ if not actual_version or actual_version < min_version:
+ print(CLIPPY_WRONG_VERSION.format(version=min_version_str))
+ return 1
+
+ cmd_args_clean = [cargo]
+ cmd_args_clean.append("clean")
+
+ cmd_args_common = ["--manifest-path"]
+ cmd_args_clippy = [cargo]
+
+ cmd_args_clippy += [
+ "clippy",
+ "--message-format=json",
+ ]
+
+ if fix:
+ cmd_args_clippy += ["--fix"]
+
+ lock_files_to_delete = []
+ for p in paths:
+ lock_file = os.path.join(p, "Cargo.lock")
+ if not os.path.exists(lock_file):
+ lock_files_to_delete.append(lock_file)
+
+ results = []
+ for p in paths:
+ # Quick sanity check of the paths
+ if p.endswith("Cargo.toml"):
+ print("Error: expects a directory or a rs file")
+ print("Found {}".format(p))
+ return 1
+
+ for p in paths:
+ onlyIn = []
+ path_conf = p
+ log.debug("Path = {}".format(p))
+ if os.path.isfile(p):
+ # We are dealing with a file. We remove the filename from the path
+ # to find the closest Cargo file
+ # We also store the name of the file to be able to filter out other
+ # files built by the cargo
+ p = os.path.dirname(p)
+ onlyIn = path_conf
+
+ elif os.path.isdir(p):
+ # Sometimes, clippy reports issues from other crates
+ # Make sure that we don't display that either
+ onlyIn = p
+
+ cargo_files = get_ancestors_by_name("Cargo.toml", p, lintargs["root"])
+ p = cargo_files[0]
+
+ log.debug("Path translated to = {}".format(p))
+ # Needs clean because of https://github.com/rust-lang/rust-clippy/issues/2604
+ clean_command = cmd_args_clean + cmd_args_common + [p]
+ run_cargo_command(log, clean_command)
+
+ # Create the actual clippy command
+ base_command = cmd_args_clippy + cmd_args_common + [p]
+ output = run_cargo_command(log, base_command)
+
+ # Remove build artifacts created by clippy
+ run_cargo_command(log, clean_command)
+
+ # Source paths in clippy spans, when they are relative, are relative to the
+ # workspace if there is one.
+ for cargo_toml in cargo_files:
+ with open(cargo_toml) as fh:
+ if "[workspace]" in fh.read():
+ p = cargo_toml
+ break
+ results += parse_issues(log, config, output, os.path.dirname(p), onlyIn)
+
+ # Remove Cargo.lock files created by clippy
+ for lock_file in lock_files_to_delete:
+ if os.path.exists(lock_file):
+ os.remove(lock_file)
+
+ return sorted(results, key=lambda issue: issue.path)
diff --git a/tools/lint/codespell.yml b/tools/lint/codespell.yml
new file mode 100644
index 0000000000..1737c12d2c
--- /dev/null
+++ b/tools/lint/codespell.yml
@@ -0,0 +1,97 @@
+---
+codespell:
+ description: Check code for common misspellings
+ include:
+ - browser/base/content/docs/
+ - browser/branding/
+ - browser/components/newtab/docs/
+ - browser/components/newtab/content-src/asrouter/docs/
+ - browser/components/places/docs/
+ - browser/components/touchbar/docs/
+ - browser/components/urlbar/docs/
+ - browser/extensions/formautofill/locales/en-US/
+ - browser/extensions/report-site-issue/locales/en-US/
+ - browser/installer/windows/docs/
+ - browser/locales/en-US/
+ - build/docs/
+ - devtools/client/locales/en-US/
+ - devtools/docs/
+ - devtools/shared/locales/en-US/
+ - devtools/startup/locales/en-US/
+ - docs/
+ - dom/docs/
+ - dom/locales/en-US/
+ - gfx/docs/
+ - intl/docs/
+ - intl/locales/en-US/
+ - ipc/docs/
+ - js/src/doc/
+ - layout/tools/layout-debug/ui/content/layoutdebug.ftl
+ - mobile/android/branding/
+ - mobile/android/docs/
+ - mobile/android/locales/en-US/
+ - netwerk/locales/en-US/
+ - netwerk/docs/
+ - python/docs/
+ - python/mach/docs/
+ - python/mozlint/
+ - python/mozperftest/perfdocs/
+ - remote/doc/
+ - security/manager/locales/en-US/
+ - services/settings/docs/
+ - services/sync/locales/en-US/
+ - taskcluster/docs/
+ - testing/docs/xpcshell/
+ - testing/geckodriver/doc/
+ - testing/mozbase/docs/
+ - testing/raptor/raptor/perfdocs/
+ - toolkit/components/extensions/docs/
+ - toolkit/components/normandy/docs/
+ - toolkit/components/search/docs/
+ - toolkit/components/telemetry/docs/
+ - toolkit/crashreporter/docs/
+ - toolkit/docs/
+ - toolkit/locales/en-US/
+ - toolkit/modules/docs/
+ - tools/code-coverage/docs/
+ - tools/fuzzing/docs/
+ - tools/lint/
+ - tools/moztreedocs/
+ - tools/profiler/docs/
+ - tools/sanitizer/docs/
+ - tools/tryselect/
+ - uriloader/docs/
+ - xpcom/docs/
+ exclude:
+ - devtools/docs/contributor/tools/storage/
+ - tools/lint/cpp/mingw-headers.txt
+ - tools/lint/test/test_codespell.py
+ - docs/mots/index.rst
+ - "**/package-lock.json"
+ # List of extensions coming from:
+ # tools/lint/{flake8,eslint}.yml
+ # tools/mach_commands.py (clang-format)
+ # + documentation
+ # + localization files
+ extensions:
+ - js
+ - jsm
+ - jxs
+ - mjs
+ - xml
+ - html
+ - xhtml
+ - cpp
+ - c
+ - h
+ - configure
+ - py
+ - properties
+ - rst
+ - md
+ - ftl
+ support-files:
+ - 'tools/lint/spell/**'
+ type: external
+ setup: spell:setup
+ payload: spell:lint
diff --git a/tools/lint/cpp/__init__.py b/tools/lint/cpp/__init__.py
new file mode 100644
index 0000000000..c580d191c1
--- /dev/null
+++ b/tools/lint/cpp/__init__.py
@@ -0,0 +1,3 @@
+# 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/.
diff --git a/tools/lint/cpp/mingw-capitalization.py b/tools/lint/cpp/mingw-capitalization.py
new file mode 100644
index 0000000000..b5a4b07c6a
--- /dev/null
+++ b/tools/lint/cpp/mingw-capitalization.py
@@ -0,0 +1,37 @@
+# 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 re
+
+from mozlint.types import LineType
+
+here = os.path.abspath(os.path.dirname(__file__))
+HEADERS_FILE = os.path.join(here, "mingw-headers.txt")
+# generated by cd mingw-w64/mingw-w64-headers &&
+# find . -name "*.h" | xargs -I bob -- basename bob | sort | uniq)
+
+
+class MinGWCapitalization(LineType):
+ def __init__(self, *args, **kwargs):
+ super(MinGWCapitalization, self).__init__(*args, **kwargs)
+ with open(HEADERS_FILE, "r") as fh:
+ self.headers = fh.read().strip().splitlines()
+ self.regex = re.compile("^#include\s*<(" + "|".join(self.headers) + ")>")
+
+ def condition(self, payload, line, config):
+ if not line.startswith("#include"):
+ return False
+
+ if self.regex.search(line, re.I):
+ return not self.regex.search(line)
+
+
+def lint(paths, config, **lintargs):
+ results = []
+
+ m = MinGWCapitalization()
+ for path in paths:
+ results.extend(m._lint(path, config, **lintargs))
+ return results
diff --git a/tools/lint/cpp/mingw-headers.txt b/tools/lint/cpp/mingw-headers.txt
new file mode 100644
index 0000000000..5ee737c393
--- /dev/null
+++ b/tools/lint/cpp/mingw-headers.txt
@@ -0,0 +1,1452 @@
+accctrl.h
+aclapi.h
+aclui.h
+acpiioct.h
+activation.h
+activaut.h
+activdbg100.h
+activdbg.h
+activecf.h
+activeds.h
+activprof.h
+activscp.h
+adc.h
+adhoc.h
+admex.h
+adoctint.h
+adodef.h
+adogpool_backcompat.h
+adogpool.h
+adoguids.h
+adoid.h
+adoint_backcompat.h
+adoint.h
+adojet.h
+adomd.h
+adptif.h
+adsdb.h
+adserr.h
+adshlp.h
+adsiid.h
+adsnms.h
+adsprop.h
+adssts.h
+adtgen.h
+advpub.h
+afilter.h
+af_irda.h
+afxres.h
+agtctl.h
+agterr.h
+agtsvr.h
+alg.h
+alink.h
+amaudio.h
+amstream.h
+amtvuids.h
+amvideo.h
+apdevpkey.h
+apisetcconv.h
+apiset.h
+appmgmt.h
+aqadmtyp.h
+asptlb.h
+assert.h
+atacct.h
+atalkwsh.h
+atm.h
+atsmedia.h
+audevcod.h
+audioapotypes.h
+audioclient.h
+audioendpoints.h
+audioengineendpoint.h
+audiopolicy.h
+audiosessiontypes.h
+austream.h
+authif.h
+authz.h
+aux_ulib.h
+avifmt.h
+aviriff.h
+avrfsdk.h
+avrt.h
+axextendenums.h
+azroles.h
+basetsd.h
+basetyps.h
+batclass.h
+bcrypt.h
+bdaiface_enums.h
+bdaiface.h
+bdamedia.h
+bdasup.h
+bdatypes.h
+bemapiset.h
+bh.h
+bidispl.h
+bits1_5.h
+bits2_0.h
+bitscfg.h
+bits.h
+bitsmsg.h
+blberr.h
+bluetoothapis.h
+_bsd_types.h
+bthdef.h
+bthsdpdef.h
+bugcodes.h
+callobj.h
+cardmod.h
+casetup.h
+cchannel.h
+cdefs.h
+cderr.h
+cdoexerr.h
+cdoex.h
+cdoexm.h
+cdoexstr.h
+cdonts.h
+cdosyserr.h
+cdosys.h
+cdosysstr.h
+celib.h
+certadm.h
+certbase.h
+certbcli.h
+certcli.h
+certenc.h
+certenroll.h
+certexit.h
+certif.h
+certmod.h
+certpol.h
+certreqd.h
+certsrv.h
+certview.h
+cfg.h
+cfgmgr32.h
+cguid.h
+chanmgr.h
+cierror.h
+classpnp.h
+clfs.h
+clfsmgmt.h
+clfsmgmtw32.h
+clfsw32.h
+client.h
+cluadmex.h
+clusapi.h
+cluscfgguids.h
+cluscfgserver.h
+cluscfgwizard.h
+cmdtree.h
+cmnquery.h
+codecapi.h
+colordlg.h
+comadmin.h
+combaseapi.h
+comcat.h
+comdef.h
+comdefsp.h
+comip.h
+comlite.h
+commapi.h
+commctrl.h
+commdlg.h
+commoncontrols.h
+complex.h
+compobj.h
+compressapi.h
+compstui.h
+comsvcs.h
+comutil.h
+confpriv.h
+conio.h
+conio_s.h
+control.h
+corecrt_startup.h
+corerror.h
+corewrappers.h
+cor.h
+corhdr.h
+correg.h
+cplext.h
+cpl.h
+credssp.h
+crtdbg.h
+crtdbg_s.h
+crtdefs.h
+cryptuiapi.h
+cryptxml.h
+cscapi.h
+cscobj.h
+csq.h
+ctfutb.h
+ctxtcall.h
+ctype.h
+custcntl.h
+_cygwin.h
+d2d1_1.h
+d2d1_1helper.h
+d2d1effectauthor.h
+d2d1effecthelpers.h
+d2d1effects.h
+d2d1.h
+d2d1helper.h
+d2dbasetypes.h
+d2derr.h
+d3d10_1.h
+d3d10_1shader.h
+d3d10effect.h
+d3d10.h
+d3d10misc.h
+d3d10sdklayers.h
+d3d10shader.h
+d3d11_1.h
+d3d11_2.h
+d3d11_3.h
+d3d11_4.h
+d3d11.h
+d3d11sdklayers.h
+d3d11shader.h
+d3d8caps.h
+d3d8.h
+d3d8types.h
+d3d9caps.h
+d3d9.h
+d3d9types.h
+d3dcaps.h
+d3dcommon.h
+d3dcompiler.h
+d3d.h
+d3dhalex.h
+d3dhal.h
+d3dnthal.h
+d3drmdef.h
+d3drm.h
+d3drmobj.h
+d3dtypes.h
+d3dx9anim.h
+d3dx9core.h
+d3dx9effect.h
+d3dx9.h
+d3dx9math.h
+d3dx9mesh.h
+d3dx9shader.h
+d3dx9shape.h
+d3dx9tex.h
+d3dx9xof.h
+d4drvif.h
+d4iface.h
+daogetrw.h
+datapath.h
+datetimeapi.h
+davclnt.h
+dbdaoerr.h
+_dbdao.h
+dbdaoid.h
+dbdaoint.h
+dbgautoattach.h
+_dbg_common.h
+dbgeng.h
+dbghelp.h
+_dbg_LOAD_IMAGE.h
+dbgprop.h
+dbt.h
+dciddi.h
+dciman.h
+dcommon.h
+dcompanimation.h
+dcomp.h
+dcomptypes.h
+dde.h
+ddeml.h
+dderror.h
+ddkernel.h
+ddkmapi.h
+ddrawgdi.h
+ddraw.h
+ddrawi.h
+ddrawint.h
+ddstream.h
+debugapi.h
+delayimp.h
+devguid.h
+devicetopology.h
+devioctl.h
+devpkey.h
+devpropdef.h
+dhcpcsdk.h
+dhcpsapi.h
+dhcpssdk.h
+dhcpv6csdk.h
+dhtmldid.h
+dhtmled.h
+dhtmliid.h
+digitalv.h
+dimm.h
+dinput.h
+direct.h
+dirent.h
+dir.h
+diskguid.h
+dispatch.h
+dispdib.h
+dispex.h
+dlcapi.h
+dlgs.h
+dls1.h
+dls2.h
+dmdls.h
+dmemmgr.h
+dmerror.h
+dmksctrl.h
+dmodshow.h
+dmo.h
+dmoreg.h
+dmort.h
+dmplugin.h
+dmusbuff.h
+dmusicc.h
+dmusicf.h
+dmusici.h
+dmusicks.h
+dmusics.h
+docobjectservice.h
+docobj.h
+documenttarget.h
+domdid.h
+dos.h
+downloadmgr.h
+dpaddr.h
+dpapi.h
+dpfilter.h
+dplay8.h
+dplay.h
+dplobby8.h
+dplobby.h
+dpnathlp.h
+driverspecs.h
+drivinit.h
+drmexternals.h
+drmk.h
+dsadmin.h
+dsclient.h
+dsconf.h
+dsdriver.h
+dsgetdc.h
+dshow.h
+dskquota.h
+dsound.h
+dsquery.h
+dsrole.h
+dssec.h
+dtchelp.h
+dvbsiparser.h
+dvdevcod.h
+dvdmedia.h
+dvec.h
+dvobj.h
+dvp.h
+dwmapi.h
+dwrite_1.h
+dwrite_2.h
+dwrite_3.h
+dwrite.h
+dxapi.h
+dxdiag.h
+dxerr8.h
+dxerr9.h
+dxfile.h
+dxgi1_2.h
+dxgi1_3.h
+dxgi1_4.h
+dxgi1_5.h
+dxgicommon.h
+dxgiformat.h
+dxgi.h
+dxgitype.h
+dxtmpl.h
+dxva2api.h
+dxva.h
+dxvahd.h
+eapauthenticatoractiondefine.h
+eapauthenticatortypes.h
+eaphosterror.h
+eaphostpeerconfigapis.h
+eaphostpeertypes.h
+eapmethodauthenticatorapis.h
+eapmethodpeerapis.h
+eapmethodtypes.h
+eappapis.h
+eaptypes.h
+edevdefs.h
+eh.h
+ehstorapi.h
+elscore.h
+emostore.h
+emptyvc.h
+endpointvolume.h
+errhandlingapi.h
+errno.h
+error.h
+errorrep.h
+errors.h
+esent.h
+evcode.h
+evcoll.h
+eventsys.h
+evntcons.h
+evntprov.h
+evntrace.h
+evr9.h
+evr.h
+exchform.h
+excpt.h
+exdisp.h
+exdispid.h
+fci.h
+fcntl.h
+fdi.h
+_fd_types.h
+fenv.h
+fibersapi.h
+fileapi.h
+fileextd.h
+file.h
+filehc.h
+filter.h
+filterr.h
+float.h
+fltdefs.h
+fltsafe.h
+fltuser.h
+fltuserstructures.h
+fltwinerror.h
+fpieee.h
+fsrmenums.h
+fsrmerr.h
+fsrm.h
+fsrmpipeline.h
+fsrmquota.h
+fsrmreports.h
+fsrmscreen.h
+ftsiface.h
+ftw.h
+functiondiscoveryapi.h
+functiondiscoverycategories.h
+functiondiscoveryconstraints.h
+functiondiscoverykeys_devpkey.h
+functiondiscoverykeys.h
+functiondiscoverynotification.h
+fusion.h
+fvec.h
+fwpmtypes.h
+fwpmu.h
+fwptypes.h
+gb18030.h
+gdiplusbase.h
+gdiplusbrush.h
+gdipluscolor.h
+gdipluscolormatrix.h
+gdipluseffects.h
+gdiplusenums.h
+gdiplusflat.h
+gdiplusgpstubs.h
+gdiplusgraphics.h
+gdiplus.h
+gdiplusheaders.h
+gdiplusimageattributes.h
+gdiplusimagecodec.h
+gdiplusimaging.h
+gdiplusimpl.h
+gdiplusinit.h
+gdipluslinecaps.h
+gdiplusmatrix.h
+gdiplusmem.h
+gdiplusmetafile.h
+gdiplusmetaheader.h
+gdipluspath.h
+gdipluspen.h
+gdipluspixelformats.h
+gdiplusstringformat.h
+gdiplustypes.h
+getopt.h
+glaux.h
+glcorearb.h
+glext.h
+gl.h
+glu.h
+glxext.h
+gpedit.h
+gpio.h
+gpmgmt.h
+guiddef.h
+h323priv.h
+handleapi.h
+heapapi.h
+hidclass.h
+hidpi.h
+hidsdi.h
+hidusage.h
+highlevelmonitorconfigurationapi.h
+hlguids.h
+hliface.h
+hlink.h
+hostinfo.h
+hstring.h
+htiface.h
+htiframe.h
+htmlguid.h
+htmlhelp.h
+httpext.h
+httpfilt.h
+http.h
+httprequestid.h
+hubbusif.h
+ia64reg.h
+iaccess.h
+iadmext.h
+iadmw.h
+iads.h
+icftypes.h
+icm.h
+icmpapi.h
+icodecapi.h
+icrsint.h
+i_cryptasn1tls.h
+ide.h
+identitycommon.h
+identitystore.h
+idf.h
+idispids.h
+iedial.h
+ieeefp.h
+ieverp.h
+ifdef.h
+iiisext.h
+iiis.h
+iimgctx.h
+iiscnfg.h
+iisrsta.h
+iketypes.h
+imagehlp.h
+ime.h
+imessage.h
+imm.h
+in6addr.h
+inaddr.h
+indexsrv.h
+inetreg.h
+inetsdk.h
+infstr.h
+initguid.h
+initoid.h
+inputscope.h
+inspectable.h
+interlockedapi.h
+internal.h
+intrin.h
+intrin-impl.h
+intsafe.h
+intshcut.h
+inttypes.h
+invkprxy.h
+ioaccess.h
+ioapiset.h
+ioevent.h
+io.h
+ipexport.h
+iphlpapi.h
+ipifcons.h
+ipinfoid.h
+ipmib.h
+_ip_mreq1.h
+ipmsp.h
+iprtrmib.h
+ipsectypes.h
+_ip_types.h
+iptypes.h
+ipxconst.h
+ipxrip.h
+ipxrtdef.h
+ipxsap.h
+ipxtfflt.h
+iscsidsc.h
+isguids.h
+issper16.h
+issperr.h
+isysmon.h
+ivec.h
+iwamreg.h
+jobapi.h
+kbdmou.h
+kcom.h
+knownfolders.h
+ksdebug.h
+ksguid.h
+ks.h
+ksmedia.h
+ksproxy.h
+ksuuids.h
+ktmtypes.h
+ktmw32.h
+kxia64.h
+l2cmn.h
+libgen.h
+libloaderapi.h
+limits.h
+lmaccess.h
+lmalert.h
+lmapibuf.h
+lmat.h
+lmaudit.h
+lmconfig.h
+lmcons.h
+lmdfs.h
+lmerr.h
+lmerrlog.h
+lm.h
+lmjoin.h
+lmmsg.h
+lmon.h
+lmremutl.h
+lmrepl.h
+lmserver.h
+lmshare.h
+lmsname.h
+lmstats.h
+lmsvc.h
+lmuseflg.h
+lmuse.h
+lmwksta.h
+loadperf.h
+locale.h
+locationapi.h
+locking.h
+lpmapi.h
+lzexpand.h
+madcapcl.h
+magnification.h
+mailmsgprops.h
+malloc.h
+manipulations.h
+mapicode.h
+mapidbg.h
+mapidefs.h
+mapiform.h
+mapiguid.h
+mapi.h
+mapihook.h
+mapinls.h
+mapioid.h
+mapispi.h
+mapitags.h
+mapiutil.h
+mapival.h
+mapiwin.h
+mapiwz.h
+mapix.h
+math.h
+mbctype.h
+mbstring.h
+mbstring_s.h
+mcd.h
+mce.h
+mciavi.h
+mcx.h
+mdcommsg.h
+mddefw.h
+mdhcp.h
+mdmsg.h
+mediaerr.h
+mediaobj.h
+medparam.h
+mem.h
+memoryapi.h
+memory.h
+mergemod.h
+mfapi.h
+mferror.h
+mfidl.h
+mfmp2dlna.h
+mfobjects.h
+mfplay.h
+mfreadwrite.h
+mftransform.h
+mgm.h
+mgmtapi.h
+midles.h
+mimedisp.h
+mimeinfo.h
+_mingw_ddk.h
+_mingw_directx.h
+_mingw_dxhelper.h
+_mingw_mac.h
+_mingw_off_t.h
+_mingw_print_pop.h
+_mingw_print_push.h
+_mingw_secapi.h
+_mingw_stat64.h
+_mingw_stdarg.h
+_mingw_unicode.h
+miniport.h
+minitape.h
+minmax.h
+minwinbase.h
+minwindef.h
+mlang.h
+mmc.h
+mmcobj.h
+mmdeviceapi.h
+mmreg.h
+mmstream.h
+mmsystem.h
+mobsync.h
+module.h
+moniker.h
+mountdev.h
+mountmgr.h
+mpeg2bits.h
+mpeg2data.h
+mpeg2psiparser.h
+mpeg2structs.h
+mprapi.h
+mprerror.h
+mq.h
+mqmail.h
+mqoai.h
+msacmdlg.h
+msacm.h
+msado15.h
+msasn1.h
+msber.h
+mscat.h
+mschapp.h
+msclus.h
+mscoree.h
+msctf.h
+msctfmonitorapi.h
+msdadc.h
+msdaguid.h
+msdaipper.h
+msdaipp.h
+msdaora.h
+msdaosp.h
+msdasc.h
+msdasql.h
+msdatsrc.h
+msdrmdefs.h
+msdrm.h
+msdshape.h
+msfs.h
+mshtmcid.h
+mshtmdid.h
+mshtmhst.h
+mshtmlc.h
+mshtml.h
+msidefs.h
+msi.h
+msimcntl.h
+msimcsdk.h
+msinkaut.h
+msiquery.h
+msoav.h
+msopc.h
+mspab.h
+mspaddr.h
+mspbase.h
+mspcall.h
+mspcoll.h
+mspenum.h
+msp.h
+msplog.h
+msports.h
+mspst.h
+mspstrm.h
+mspterm.h
+mspthrd.h
+msptrmac.h
+msptrmar.h
+msptrmvc.h
+msputils.h
+msrdc.h
+msremote.h
+mssip.h
+msstkppg.h
+mstask.h
+mstcpip.h
+msterr.h
+mswsock.h
+msxml2did.h
+msxml2.h
+msxmldid.h
+msxml.h
+mtsadmin.h
+mtsevents.h
+mtsgrp.h
+mtxadmin.h
+mtxattr.h
+mtxdm.h
+mtx.h
+muiload.h
+multimon.h
+multinfo.h
+mxdc.h
+namedpipeapi.h
+namespaceapi.h
+napcertrelyingparty.h
+napcommon.h
+napenforcementclient.h
+napmanagement.h
+napmicrosoftvendorids.h
+napprotocol.h
+napservermanagement.h
+napsystemhealthagent.h
+napsystemhealthvalidator.h
+naptypes.h
+naputil.h
+nb30.h
+ncrypt.h
+ndattrib.h
+ndfapi.h
+ndhelper.h
+ndisguid.h
+ndis.h
+ndistapi.h
+ndiswan.h
+ndkinfo.h
+ndr64types.h
+ndrtypes.h
+netcon.h
+neterr.h
+netevent.h
+netfw.h
+netioapi.h
+netlistmgr.h
+netmon.h
+netpnp.h
+netprov.h
+nettypes.h
+newapis.h
+newdev.h
+new.h
+nldef.h
+nmsupp.h
+npapi.h
+nsemail.h
+nspapi.h
+ntagp.h
+ntdd1394.h
+ntdd8042.h
+ntddbeep.h
+ntddcdrm.h
+ntddcdvd.h
+ntddchgr.h
+ntdddisk.h
+ntddft.h
+ntddkbd.h
+ntddk.h
+ntddmmc.h
+ntddmodm.h
+ntddmou.h
+ntddndis.h
+ntddpar.h
+ntddpcm.h
+ntddpsch.h
+ntddscsi.h
+ntddser.h
+ntddsnd.h
+ntddstor.h
+ntddtape.h
+ntddtdi.h
+ntddvdeo.h
+ntddvol.h
+ntdef.h
+ntdsapi.h
+ntdsbcli.h
+ntdsbmsg.h
+ntgdi.h
+ntifs.h
+ntimage.h
+ntiologc.h
+ntldap.h
+ntmsapi.h
+ntmsmli.h
+ntnls.h
+ntpoapi.h
+ntquery.h
+ntsdexts.h
+ntsecapi.h
+ntsecpkg.h
+ntstatus.h
+ntstrsafe.h
+ntverp.h
+oaidl.h
+objbase.h
+objectarray.h
+objerror.h
+objidlbase.h
+objidl.h
+objsafe.h
+objsel.h
+ocidl.h
+ocmm.h
+odbcinst.h
+odbcss.h
+ole2.h
+ole2ver.h
+oleacc.h
+oleauto.h
+olectl.h
+olectlid.h
+oledbdep.h
+oledberr.h
+oledbguid.h
+oledb.h
+oledlg.h
+ole.h
+oleidl.h
+oletx2xa.h
+opmapi.h
+oprghdlr.h
+optary.h
+p2p.h
+packoff.h
+packon.h
+parallel.h
+param.h
+parser.h
+patchapi.h
+patchwiz.h
+pathcch.h
+pbt.h
+pchannel.h
+pciprop.h
+pcrt32.h
+pdh.h
+pdhmsg.h
+penwin.h
+perflib.h
+perhist.h
+persist.h
+pfhook.h
+pgobootrun.h
+physicalmonitorenumerationapi.h
+pla.h
+pnrpdef.h
+pnrpns.h
+poclass.h
+polarity.h
+_pop_BOOL.h
+poppack.h
+portabledeviceconnectapi.h
+portabledevicetypes.h
+portcls.h
+powrprof.h
+prnasnot.h
+prntfont.h
+processenv.h
+process.h
+processthreadsapi.h
+processtopologyapi.h
+profileapi.h
+profile.h
+profinfo.h
+propidl.h
+propkeydef.h
+propkey.h
+propsys.h
+propvarutil.h
+prsht.h
+psapi.h
+pshpack1.h
+pshpack2.h
+pshpack4.h
+pshpack8.h
+pshpck16.h
+pstore.h
+pthread_signal.h
+pthread_time.h
+pthread_unistd.h
+punknown.h
+_push_BOOL.h
+qedit.h
+qmgr.h
+qnetwork.h
+qos2.h
+qos.h
+qosname.h
+qospol.h
+qossp.h
+rasdlg.h
+raseapif.h
+raserror.h
+ras.h
+rassapi.h
+rasshost.h
+ratings.h
+rdpencomapi.h
+realtimeapiset.h
+reason.h
+recguids.h
+reconcil.h
+regbag.h
+regstr.h
+rend.h
+resapi.h
+restartmanager.h
+richedit.h
+richole.h
+rkeysvcc.h
+rnderr.h
+roapi.h
+routprot.h
+rpcasync.h
+rpcdce.h
+rpcdcep.h
+rpc.h
+rpcndr.h
+rpcnsi.h
+rpcnsip.h
+rpcnterr.h
+rpcproxy.h
+rpcsal.h
+rpcssl.h
+rrascfg.h
+rtcapi.h
+rtccore.h
+rtcerr.h
+rtinfo.h
+rtm.h
+rtmv2.h
+rtutils.h
+sal.h
+sapi51.h
+sapi53.h
+sapi54.h
+sapi.h
+sas.h
+sbe.h
+scarddat.h
+scarderr.h
+scardmgr.h
+scardsrv.h
+scardssp.h
+scesvc.h
+schannel.h
+schedule.h
+schemadef.h
+schnlsp.h
+scode.h
+scrnsave.h
+scrptids.h
+scsi.h
+scsiscan.h
+scsiwmi.h
+sddl.h
+sdkddkver.h
+sdoias.h
+sdpblb.h
+sdperr.h
+search.h
+search_s.h
+secext.h
+securityappcontainer.h
+securitybaseapi.h
+security.h
+sehmap.h
+sensapi.h
+sensevts.h
+sens.h
+sensorsapi.h
+sensors.h
+servprov.h
+setjmpex.h
+setjmp.h
+setupapi.h
+sfc.h
+shappmgr.h
+share.h
+shdeprecated.h
+shdispid.h
+shellapi.h
+sherrors.h
+shfolder.h
+shldisp.h
+shlguid.h
+shlobj.h
+shlwapi.h
+shobjidl.h
+shtypes.h
+signal.h
+simpdata.h
+simpdc.h
+sipbase.h
+sisbkup.h
+slerror.h
+slpublic.h
+smbus.h
+smpab.h
+smpms.h
+smpxp.h
+smtpguid.h
+smx.h
+snmp.h
+_socket_types.h
+softpub.h
+specstrings.h
+sperror.h
+sphelper.h
+sporder.h
+sql_1.h
+sqlext.h
+sql.h
+sqloledb.h
+sqltypes.h
+sqlucode.h
+srb.h
+srrestoreptapi.h
+srv.h
+sspguid.h
+sspi.h
+sspserr.h
+sspsidl.h
+stat.h
+stdarg.h
+stddef.h
+stdexcpt.h
+stdint.h
+stdio.h
+stdio_s.h
+stdlib.h
+stdlib_s.h
+stdunk.h
+stierr.h
+sti.h
+stireg.h
+stllock.h
+stm.h
+storage.h
+storduid.h
+storport.h
+storprop.h
+stralign.h
+stralign_s.h
+stringapiset.h
+string.h
+string_s.h
+strings.h
+strmif.h
+strmini.h
+strsafe.h
+structuredquerycondition.h
+subauth.h
+subsmgr.h
+svcguid.h
+svrapi.h
+swenum.h
+synchapi.h
+sysinfoapi.h
+syslimits.h
+systemtopologyapi.h
+t2embapi.h
+tabflicks.h
+tapi3cc.h
+tapi3ds.h
+tapi3err.h
+tapi3.h
+tapi3if.h
+tapi.h
+taskschd.h
+tbs.h
+tcerror.h
+tcguid.h
+tchar.h
+tchar_s.h
+tcpestats.h
+tcpmib.h
+tdh.h
+tdi.h
+tdiinfo.h
+tdikrnl.h
+tdistat.h
+termmgr.h
+textserv.h
+textstor.h
+threadpoolapiset.h
+threadpoollegacyapiset.h
+timeb.h
+timeb_s.h
+time.h
+timeprov.h
+_timeval.h
+timezoneapi.h
+tlbref.h
+tlhelp32.h
+tlogstg.h
+tmschema.h
+tnef.h
+tom.h
+tpcshrd.h
+traffic.h
+transact.h
+triedcid.h
+triediid.h
+triedit.h
+tsattrs.h
+tspi.h
+tssbx.h
+tsuserex.h
+tuner.h
+tvout.h
+txcoord.h
+txctx.h
+txdtc.h
+txfw32.h
+typeinfo.h
+types.h
+uastrfnc.h
+uchar.h
+udpmib.h
+uianimation.h
+uiautomationclient.h
+uiautomationcoreapi.h
+uiautomationcore.h
+uiautomation.h
+uiviewsettingsinterop.h
+umx.h
+unistd.h
+unknown.h
+unknwnbase.h
+unknwn.h
+upssvc.h
+urlhist.h
+urlmon.h
+usb100.h
+usb200.h
+usbbusif.h
+usbcamdi.h
+usbdi.h
+usbdlib.h
+usbdrivr.h
+usb.h
+usbioctl.h
+usbiodef.h
+usbkern.h
+usbprint.h
+usbprotocoldefs.h
+usbrpmif.h
+usbscan.h
+usbspec.h
+usbstorioctl.h
+usbuser.h
+userenv.h
+usp10.h
+utilapiset.h
+utime.h
+uuids.h
+uxtheme.h
+vadefs.h
+varargs.h
+_varenum.h
+vcr.h
+vdmdbg.h
+vds.h
+vdslun.h
+versionhelpers.h
+vfw.h
+vfwmsgs.h
+videoagp.h
+video.h
+virtdisk.h
+vmr9.h
+vsadmin.h
+vsbackup.h
+vsmgmt.h
+vsprov.h
+vss.h
+vsstyle.h
+vssym32.h
+vswriter.h
+w32api.h
+wabapi.h
+wabcode.h
+wabdefs.h
+wab.h
+wabiab.h
+wabmem.h
+wabnot.h
+wabtags.h
+wabutil.h
+wbemads.h
+wbemcli.h
+wbemdisp.h
+wbemidl.h
+wbemprov.h
+wbemtran.h
+wchar.h
+wchar_s.h
+wcmconfig.h
+wcsplugin.h
+wct.h
+wctype.h
+wdmguid.h
+wdm.h
+wdsbp.h
+wdsclientapi.h
+wdspxe.h
+wdstci.h
+wdstpdi.h
+wdstptmgmt.h
+werapi.h
+wfext.h
+wglext.h
+wiadef.h
+wiadevd.h
+wia.h
+wiavideo.h
+winable.h
+winapifamily.h
+winbase.h
+winber.h
+wincodec.h
+wincon.h
+wincred.h
+wincrypt.h
+winddi.h
+winddiui.h
+windef.h
+windns.h
+windot11.h
+windows.foundation.h
+windows.h
+windows.security.cryptography.h
+windows.storage.h
+windows.storage.streams.h
+windows.system.threading.h
+windowsx.h
+winefs.h
+winerror.h
+winevt.h
+wingdi.h
+winhttp.h
+wininet.h
+winineti.h
+winioctl.h
+winldap.h
+winnetwk.h
+winnls32.h
+winnls.h
+winnt.h
+winperf.h
+winreg.h
+winresrc.h
+winsafer.h
+winsatcominterfacei.h
+winscard.h
+winsdkver.h
+winsmcrd.h
+winsnmp.h
+winsock2.h
+winsock.h
+winsplp.h
+winspool.h
+winstring.h
+winsvc.h
+winsxs.h
+winsync.h
+winternl.h
+wintrust.h
+winusb.h
+winusbio.h
+winuser.h
+winver.h
+winwlx.h
+wlanapi.h
+wlanihvtypes.h
+wlantypes.h
+wmcodecdsp.h
+wmcontainer.h
+wmdrmsdk.h
+wmiatlprov.h
+wmidata.h
+wmilib.h
+wmistr.h
+wmiutils.h
+wmsbuffer.h
+wmsdkidl.h
+wnnc.h
+wow64apiset.h
+wownt16.h
+wownt32.h
+wpapi.h
+wpapimsg.h
+wpcapi.h
+wpcevent.h
+wpcrsmsg.h
+wpftpmsg.h
+wppstmsg.h
+wpspihlp.h
+wptypes.h
+wpwizmsg.h
+wrl.h
+_ws1_undef.h
+ws2atm.h
+ws2bth.h
+ws2def.h
+ws2dnet.h
+ws2ipdef.h
+ws2san.h
+ws2spi.h
+ws2tcpip.h
+_wsadata.h
+_wsa_errnos.h
+wsdapi.h
+wsdattachment.h
+wsdbase.h
+wsdclient.h
+wsddisco.h
+wsdhost.h
+wsdtypes.h
+wsdutil.h
+wsdxmldom.h
+wsdxml.h
+wshisotp.h
+wsipv6ok.h
+wsipx.h
+wsmandisp.h
+wsman.h
+wsnetbs.h
+wsnwlink.h
+wspiapi.h
+wsrm.h
+wsvns.h
+wtsapi32.h
+wtypesbase.h
+wtypes.h
+xa.h
+xcmcext.h
+xcmc.h
+xcmcmsx2.h
+xcmcmsxt.h
+xenroll.h
+xfilter.h
+xinput.h
+xlocinfo.h
+xmath.h
+_xmitfile.h
+xmldomdid.h
+xmldsodid.h
+xmllite.h
+xmltrnsf.h
+xolehlp.h
+xpsdigitalsignature.h
+xpsobjectmodel_1.h
+xpsobjectmodel.h
+xpsprint.h
+xpsrassvc.h
+ymath.h
+yvals.h
+zmouse.h
diff --git a/tools/lint/eslint.yml b/tools/lint/eslint.yml
new file mode 100644
index 0000000000..e17334f9d4
--- /dev/null
+++ b/tools/lint/eslint.yml
@@ -0,0 +1,32 @@
+---
+eslint:
+ description: JavaScript linter
+ # ESLint infra handles its own path filtering, so just include cwd
+ include: ['.']
+ exclude: []
+ # When adding to this list, consider updating hooks_js_format.py as well.
+ extensions: ['mjs', 'js', 'jsm', 'jsx', 'html', 'sjs', 'xhtml']
+ support-files:
+ - '**/.eslintrc.js'
+ - '.eslintrc-test-paths.js'
+ - '.eslintignore'
+ - 'tools/lint/eslint/**'
+ # Files that can influence global variables
+ - 'browser/base/content/nsContextMenu.js'
+ - 'browser/base/content/utilityOverlay.js'
+ - 'browser/components/customizableui/content/panelUI.js'
+ - 'browser/components/downloads/content/downloads.js'
+ - 'browser/components/downloads/content/indicator.js'
+ - 'testing/mochitest/tests/SimpleTest/EventUtils.js'
+ - 'testing/mochitest/tests/SimpleTest/MockObjects.js'
+ - 'testing/mochitest/tests/SimpleTest/SimpleTest.js'
+ - 'testing/mochitest/tests/SimpleTest/WindowSnapshot.js'
+ - 'toolkit/components/printing/content/printUtils.js'
+ - 'toolkit/components/viewsource/content/viewSourceUtils.js'
+ - 'toolkit/content/contentAreaUtils.js'
+ - 'toolkit/content/editMenuOverlay.js'
+ - 'toolkit/content/globalOverlay.js'
+ - 'toolkit/modules/Services.jsm'
+ type: external
+ payload: eslint:lint
+ setup: eslint:setup
diff --git a/tools/lint/eslint/.eslintrc.js b/tools/lint/eslint/.eslintrc.js
new file mode 100644
index 0000000000..ff0ef234ea
--- /dev/null
+++ b/tools/lint/eslint/.eslintrc.js
@@ -0,0 +1,27 @@
+/* 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/. */
+
+"use strict";
+
+module.exports = {
+ // eslint-plugin-mozilla runs under node, so we need a more restrictive
+ // environment / parser setup here than the rest of mozilla-central.
+ env: {
+ browser: false,
+ node: true,
+ },
+ parser: "espree",
+ parserOptions: {
+ ecmaVersion: 12,
+ },
+
+ rules: {
+ camelcase: ["error", { properties: "never" }],
+ "handle-callback-err": ["error", "er"],
+ "no-shadow": "error",
+ "no-undef-init": "error",
+ "one-var": ["error", "never"],
+ strict: ["error", "global"],
+ },
+};
diff --git a/tools/lint/eslint/__init__.py b/tools/lint/eslint/__init__.py
new file mode 100644
index 0000000000..af3f5efc56
--- /dev/null
+++ b/tools/lint/eslint/__init__.py
@@ -0,0 +1,156 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 json
+import os
+import signal
+import subprocess
+import sys
+
+sys.path.append(os.path.join(os.path.dirname(__file__), "eslint"))
+from eslint import setup_helper
+from mozbuild.nodeutil import find_node_executable
+from mozlint import result
+
+ESLINT_ERROR_MESSAGE = """
+An error occurred running eslint. Please check the following error messages:
+
+{}
+""".strip()
+
+ESLINT_NOT_FOUND_MESSAGE = """
+Could not find eslint! We looked at the --binary option, at the ESLINT
+environment variable, and then at your local node_modules path. Please Install
+eslint and needed plugins with:
+
+mach eslint --setup
+
+and try again.
+""".strip()
+
+
+def setup(root, **lintargs):
+ setup_helper.set_project_root(root)
+
+ if not setup_helper.check_node_executables_valid():
+ return 1
+
+ return setup_helper.eslint_maybe_setup()
+
+
+def lint(paths, config, binary=None, fix=None, rules=[], setup=None, **lintargs):
+ """Run eslint."""
+ log = lintargs["log"]
+ setup_helper.set_project_root(lintargs["root"])
+ module_path = setup_helper.get_project_root()
+
+ # Valid binaries are:
+ # - Any provided by the binary argument.
+ # - Any pointed at by the ESLINT environmental variable.
+ # - Those provided by |mach lint --setup|.
+
+ if not binary:
+ binary, _ = find_node_executable()
+
+ if not binary:
+ print(ESLINT_NOT_FOUND_MESSAGE)
+ return 1
+
+ extra_args = lintargs.get("extra_args") or []
+ exclude_args = []
+ for path in config.get("exclude", []):
+ exclude_args.extend(
+ ["--ignore-pattern", os.path.relpath(path, lintargs["root"])]
+ )
+
+ for rule in rules:
+ extra_args.extend(["--rule", rule])
+
+ cmd_args = (
+ [
+ binary,
+ os.path.join(module_path, "node_modules", "eslint", "bin", "eslint.js"),
+ # This keeps ext as a single argument.
+ "--ext",
+ "[{}]".format(",".join(config["extensions"])),
+ "--format",
+ "json",
+ "--no-error-on-unmatched-pattern",
+ ]
+ + rules
+ + extra_args
+ + exclude_args
+ + paths
+ )
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ results = run(cmd_args, config)
+ fixed = 0
+ # eslint requires that --fix be set before the --ext argument.
+ if fix:
+ fixed += len(results)
+ cmd_args.insert(2, "--fix")
+ results = run(cmd_args, config)
+ fixed = fixed - len(results)
+
+ return {"results": results, "fixed": fixed}
+
+
+def run(cmd_args, config):
+
+ shell = False
+ if (
+ os.environ.get("MSYSTEM") in ("MINGW32", "MINGW64")
+ or "MOZILLABUILD" in os.environ
+ ):
+ # The eslint binary needs to be run from a shell with msys
+ shell = True
+ encoding = "utf-8"
+
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ proc = subprocess.Popen(
+ cmd_args, shell=shell, stdout=subprocess.PIPE, stderr=subprocess.PIPE
+ )
+ signal.signal(signal.SIGINT, orig)
+
+ try:
+ output, errors = proc.communicate()
+ except KeyboardInterrupt:
+ proc.kill()
+ return []
+
+ if errors:
+ errors = errors.decode(encoding, "replace")
+ print(ESLINT_ERROR_MESSAGE.format(errors))
+
+ if proc.returncode >= 2:
+ return 1
+
+ if not output:
+ return [] # no output means success
+ output = output.decode(encoding, "replace")
+ try:
+ jsonresult = json.loads(output)
+ except ValueError:
+ print(ESLINT_ERROR_MESSAGE.format(output))
+ return 1
+
+ results = []
+ for obj in jsonresult:
+ errors = obj["messages"]
+
+ for err in errors:
+ err.update(
+ {
+ "hint": err.get("fix"),
+ "level": "error" if err["severity"] == 2 else "warning",
+ "lineno": err.get("line") or 0,
+ "path": obj["filePath"],
+ "rule": err.get("ruleId"),
+ }
+ )
+ results.append(result.from_config(config, **err))
+
+ return results
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/.npmignore b/tools/lint/eslint/eslint-plugin-mozilla/.npmignore
new file mode 100644
index 0000000000..3713448c7a
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/.npmignore
@@ -0,0 +1,8 @@
+.eslintrc.js
+.npmignore
+node_modules
+reporters
+scripts
+tests
+package-lock.json
+update.sh
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/LICENSE b/tools/lint/eslint/eslint-plugin-mozilla/LICENSE
new file mode 100644
index 0000000000..e87a115e46
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/LICENSE
@@ -0,0 +1,363 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. "Contributor"
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the terms of
+ a Secondary License.
+
+1.6. "Executable Form"
+
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+
+ means a work that combines Covered Software with other material, in a
+ separate file or files, that is not Covered Software.
+
+1.8. "License"
+
+ means this document.
+
+1.9. "Licensable"
+
+ means having the right to grant, to the maximum extent possible, whether
+ at the time of the initial grant or subsequently, any and all of the
+ rights conveyed by this License.
+
+1.10. "Modifications"
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. "Patent Claims" of a Contributor
+
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the License,
+ by the making, using, selling, offering for sale, having made, import,
+ or transfer of either its Contributions or its Contributor Version.
+
+1.12. "Secondary License"
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. "Source Code Form"
+
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, "control" means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+ This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with
+ the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this
+ License (see Section 10.2) or under the terms of a Secondary License (if
+ permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights to
+ grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under
+ the terms of this License. You must inform recipients that the Source
+ Code Form of the Covered Software is governed by the terms of this
+ License, and how they can obtain a copy of this License. You may not
+ attempt to alter or restrict the recipients' rights in the Source Code
+ Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter the
+ recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for
+ the Covered Software. If the Larger Work is a combination of Covered
+ Software with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient of
+ the Larger Work may, at their option, further distribute the Covered
+ Software under the terms of either this License or such Secondary
+ License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty, or
+ limitations of liability) contained within the Source Code Form of the
+ Covered Software, except that You may alter any license notices to the
+ extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on
+ behalf of any Contributor. You must make it absolutely clear that any
+ such warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute,
+ judicial order, or regulation then You must: (a) comply with the terms of
+ this License to the maximum extent possible; and (b) describe the
+ limitations and the code they affect. Such description must be placed in a
+ text file included with all distributions of the Covered Software under
+ this License. Except to the extent prohibited by statute or regulation,
+ such description must be sufficiently detailed for a recipient of ordinary
+ skill to be able to understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing
+ basis, if such Contributor fails to notify You of the non-compliance by
+ some reasonable means prior to 60 days after You have come back into
+ compliance. Moreover, Your grants from a particular Contributor are
+ reinstated on an ongoing basis if such Contributor notifies You of the
+ non-compliance by some reasonable means, this is the first time You have
+ received notice of non-compliance with this License from such
+ Contributor, and You become compliant prior to 30 days after Your receipt
+ of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor Version
+ directly or indirectly infringes any patent, then the rights granted to
+ You by any and all Contributors for the Covered Software under Section
+ 2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an "as is" basis,
+ without warranty of any kind, either expressed, implied, or statutory,
+ including, without limitation, warranties that the Covered Software is free
+ of defects, merchantable, fit for a particular purpose or non-infringing.
+ The entire risk as to the quality and performance of the Covered Software
+ is with You. Should any Covered Software prove defective in any respect,
+ You (not any Contributor) assume the cost of any necessary servicing,
+ repair, or correction. This disclaimer of warranty constitutes an essential
+ part of this License. No use of any Covered Software is authorized under
+ this License except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from
+ such party's negligence to the extent applicable law prohibits such
+ limitation. Some jurisdictions do not allow the exclusion or limitation of
+ incidental or consequential damages, so this exclusion and limitation may
+ not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts
+ of a jurisdiction where the defendant maintains its principal place of
+ business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions. Nothing
+ in this Section shall prevent a party's ability to bring cross-claims or
+ counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides that
+ the language of a contract shall be construed against the drafter shall not
+ be used to construe this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software,
+ or under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a
+ modified version of this License if you rename the license and remove
+ any references to the name of the license steward (except to note that
+ such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses If You choose to distribute Source Code Form that is
+ Incompatible With Secondary Licenses under the terms of this version of
+ the License, the notice described in Exhibit B of this License must be
+ attached.
+
+Exhibit A - Source Code Form License Notice
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular file,
+then You may include the notice in a location (such as a LICENSE file in a
+relevant directory) where a recipient would be likely to look for such a
+notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+
+ This Source Code Form is "Incompatible
+ With Secondary Licenses", as defined by
+ the Mozilla Public License, v. 2.0.
+
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/README.md b/tools/lint/eslint/eslint-plugin-mozilla/README.md
new file mode 100644
index 0000000000..650507754e
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/README.md
@@ -0,0 +1,56 @@
+# eslint-plugin-mozilla
+
+A collection of rules that help enforce JavaScript coding standard in the Mozilla project.
+
+These are primarily developed and used within the Firefox build system ([mozilla-central](https://hg.mozilla.org/mozilla-central/)), but are made available for other
+related projects to use as well.
+
+## Installation
+
+### Within mozilla-central:
+
+```
+$ ./mach eslint --setup
+```
+
+### Outside mozilla-central:
+
+Install ESLint [ESLint](http://eslint.org):
+
+```
+$ npm i eslint --save-dev
+```
+
+Next, install `eslint-plugin-mozilla`:
+
+```
+$ npm install eslint-plugin-mozilla --save-dev
+```
+
+## Documentation
+
+For details about the rules, please see the [firefox documentation page](http://firefox-source-docs.mozilla.org/tools/lint/linters/eslint-plugin-mozilla.html).
+
+## Source Code
+
+The sources can be found at:
+
+* Code: https://searchfox.org/mozilla-central/source/tools/lint/eslint/eslint-plugin-mozilla
+* Documentation: https://searchfox.org/mozilla-central/source/docs/code-quality/lint/linters
+
+## Bugs
+
+Please file bugs in Bugzilla in the Lint component of the Testing product.
+
+* [Existing bugs](https://bugzilla.mozilla.org/buglist.cgi?resolution=---&query_format=advanced&component=Lint&product=Testing)
+* [New bugs](https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=Lint)
+
+## Tests
+
+The tests can only be run from within mozilla-central. To run the tests:
+
+```
+./mach eslint --setup
+cd tools/lint/eslint/eslint-plugin-mozilla
+npm run test
+```
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/.eslintrc.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/.eslintrc.js
new file mode 100644
index 0000000000..76df4134f5
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/.eslintrc.js
@@ -0,0 +1,8 @@
+"use strict";
+
+module.exports = {
+ rules: {
+ // Require object keys to be sorted.
+ "sort-keys": "error",
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/browser-test.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/browser-test.js
new file mode 100644
index 0000000000..5c54d4bd62
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/browser-test.js
@@ -0,0 +1,88 @@
+// Parent config file for all browser-chrome files.
+"use strict";
+
+module.exports = {
+ env: {
+ browser: true,
+ "mozilla/browser-window": true,
+ "mozilla/simpletest": true,
+ // "node": true
+ },
+
+ // All globals made available in the test environment.
+ globals: {
+ // `$` is defined in SimpleTest.js
+ $: false,
+ Assert: false,
+ BrowserTestUtils: false,
+ ContentTask: false,
+ ContentTaskUtils: false,
+ EventUtils: false,
+ IOUtils: false,
+ PathUtils: false,
+ PromiseDebugging: false,
+ SpecialPowers: false,
+ TestUtils: false,
+ XPCNativeWrapper: false,
+ addLoadEvent: false,
+ add_setup: false,
+ add_task: false,
+ content: false,
+ executeSoon: false,
+ expectUncaughtException: false,
+ export_assertions: false,
+ extractJarToTmp: false,
+ finish: false,
+ gTestPath: false,
+ getChromeDir: false,
+ getJar: false,
+ getResolvedURI: false,
+ getRootDirectory: false,
+ getTestFilePath: false,
+ ignoreAllUncaughtExceptions: false,
+ info: false,
+ is: false,
+ isnot: false,
+ ok: false,
+ record: false,
+ registerCleanupFunction: false,
+ requestLongerTimeout: false,
+ setExpectedFailuresForSelfTest: false,
+ stringContains: false,
+ stringMatches: false,
+ todo: false,
+ todo_is: false,
+ todo_isnot: false,
+ waitForClipboard: false,
+ waitForExplicitFinish: false,
+ waitForFocus: false,
+ },
+
+ plugins: ["mozilla", "@microsoft/sdl"],
+
+ rules: {
+ // No using of insecure url, so no http urls
+ "@microsoft/sdl/no-insecure-url": [
+ "error",
+ {
+ exceptions: [
+ "^http:\\/\\/mochi\\.test?.*",
+ "^http:\\/\\/localhost?.*",
+ "^http:\\/\\/127\\.0\\.0\\.1?.*",
+ // Exempt xmlns urls
+ "^http:\\/\\/www\\.w3\\.org?.*",
+ "^http:\\/\\/www\\.mozilla\\.org\\/keymaster\\/gatekeeper?.*",
+ // Exempt urls that start with ftp or ws.
+ "^ws:?.*",
+ "^ftp:?.*",
+ ],
+ varExceptions: ["insecure?.*"],
+ },
+ ],
+ "mozilla/import-content-task-globals": "error",
+ "mozilla/import-headjs-globals": "error",
+ "mozilla/mark-test-function-used": "error",
+ "mozilla/no-addtask-setup": "error",
+ "mozilla/no-arbitrary-setTimeout": "error",
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/chrome-test.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/chrome-test.js
new file mode 100644
index 0000000000..bc9c5050b7
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/chrome-test.js
@@ -0,0 +1,59 @@
+// Parent config file for all mochitest files.
+"use strict";
+
+module.exports = {
+ env: {
+ browser: true,
+ "mozilla/browser-window": true,
+ },
+
+ // All globals made available in the test environment.
+ globals: {
+ // SpecialPowers is injected into the window object via SimpleTest.js
+ SpecialPowers: false,
+ XPCNativeWrapper: false,
+ extractJarToTmp: false,
+ getChromeDir: false,
+ getJar: false,
+ getResolvedURI: false,
+ getRootDirectory: false,
+ },
+
+ overrides: [
+ {
+ env: {
+ // Ideally we wouldn't be using the simpletest env here, but our uses of
+ // js files mean we pick up everything from the global scope, which could
+ // be any one of a number of html files. So we just allow the basics...
+ "mozilla/simpletest": true,
+ },
+ files: ["*.js"],
+ },
+ ],
+
+ plugins: ["mozilla", "@microsoft/sdl"],
+
+ rules: {
+ // No using of insecure url, so no http urls
+ "@microsoft/sdl/no-insecure-url": [
+ "error",
+ {
+ exceptions: [
+ "^http:\\/\\/mochi\\.test?.*",
+ "^http:\\/\\/localhost?.*",
+ "^http:\\/\\/127\\.0\\.0\\.1?.*",
+ // Exempt xmlns urls
+ "^http:\\/\\/www\\.w3\\.org?.*",
+ "^http:\\/\\/www\\.mozilla\\.org\\/keymaster\\/gatekeeper?.*",
+ // Exempt urls that start with ftp or ws.
+ "^ws:?.*",
+ "^ftp:?.*",
+ ],
+ varExceptions: ["insecure?.*"],
+ },
+ ],
+ "mozilla/import-content-task-globals": "error",
+ "mozilla/import-headjs-globals": "error",
+ "mozilla/mark-test-function-used": "error",
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/mochitest-test.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/mochitest-test.js
new file mode 100644
index 0000000000..c08a6db4d2
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/mochitest-test.js
@@ -0,0 +1,57 @@
+// Parent config file for all mochitest files.
+"use strict";
+
+module.exports = {
+ env: {
+ browser: true,
+ },
+
+ // All globals made available in the test environment.
+ globals: {
+ // SpecialPowers is injected into the window object via SimpleTest.js
+ SpecialPowers: false,
+ XPCNativeWrapper: false,
+ },
+
+ overrides: [
+ {
+ env: {
+ // Ideally we wouldn't be using the simpletest env here, but our uses of
+ // js files mean we pick up everything from the global scope, which could
+ // be any one of a number of html files. So we just allow the basics...
+ "mozilla/simpletest": true,
+ },
+ files: ["*.js"],
+ },
+ ],
+ plugins: ["mozilla", "@microsoft/sdl"],
+
+ rules: {
+ // No using of insecure url, so no http urls
+ "@microsoft/sdl/no-insecure-url": [
+ "error",
+ {
+ exceptions: [
+ "^http:\\/\\/mochi\\.test?.*",
+ "^http:\\/\\/mochi\\.xorigin-test?.*",
+ "^http:\\/\\/localhost?.*",
+ "^http:\\/\\/127\\.0\\.0\\.1?.*",
+ // Exempt xmlns urls
+ "^http:\\/\\/www\\.w3\\.org?.*",
+ "^http:\\/\\/www\\.mozilla\\.org\\/keymaster\\/gatekeeper?.*",
+ // Exempt urls that start with ftp or ws.
+ "^ws:?.*",
+ "^ftp:?.*",
+ ],
+ varExceptions: ["insecure?.*"],
+ },
+ ],
+ "mozilla/import-content-task-globals": "error",
+ "mozilla/import-headjs-globals": "error",
+ "mozilla/mark-test-function-used": "error",
+ // Turn off no-define-cc-etc for mochitests as these don't have Cc etc defined in the
+ // global scope.
+ "mozilla/no-define-cc-etc": "off",
+ "no-shadow": "error",
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
new file mode 100644
index 0000000000..64aa7d36c0
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/recommended.js
@@ -0,0 +1,348 @@
+/* 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/. */
+
+"use strict";
+
+/**
+ * The configuration is based on eslint:recommended config. The details for all
+ * the ESLint rules, and which ones are in the recommended configuration can
+ * be found here:
+ *
+ * https://eslint.org/docs/rules/
+ */
+module.exports = {
+ env: {
+ browser: true,
+ es2021: true,
+ "mozilla/privileged": true,
+ "mozilla/specific": true,
+ },
+
+ extends: ["eslint:recommended", "plugin:prettier/recommended"],
+
+ overrides: [
+ {
+ // System mjs files and jsm files are not loaded in the browser scope,
+ // so we turn that off for those. Though we do have our own special
+ // environment for them.
+ env: {
+ browser: false,
+ "mozilla/jsm": true,
+ },
+ files: ["**/*.sys.mjs", "**/*.jsm", "**/*.jsm.js"],
+ rules: {
+ "mozilla/lazy-getter-object-name": "error",
+ "mozilla/reject-eager-module-in-lazy-getter": "error",
+ "mozilla/reject-global-this": "error",
+ "mozilla/reject-globalThis-modification": "error",
+ // For all system modules, we expect no properties to need importing,
+ // hence reject everything.
+ "mozilla/reject-importGlobalProperties": ["error", "everything"],
+ "mozilla/reject-mixing-eager-and-lazy": "error",
+ "mozilla/reject-top-level-await": "error",
+ // TODO: Bug 1575506 turn `builtinGlobals` on here.
+ // We can enable builtinGlobals for jsms due to their scopes.
+ "no-redeclare": ["error", { builtinGlobals: false }],
+ },
+ },
+ {
+ files: ["**/*.mjs", "**/*.jsm"],
+ rules: {
+ // Modules are far easier to check for no-unused-vars on a global scope,
+ // than our content files. Hence we turn that on here.
+ "no-unused-vars": [
+ "error",
+ {
+ args: "none",
+ vars: "all",
+ },
+ ],
+ },
+ },
+ {
+ files: ["**/*.sys.mjs"],
+ rules: {
+ "mozilla/use-static-import": "error",
+ },
+ },
+ {
+ excludedFiles: ["**/*.sys.mjs"],
+ files: ["**/*.mjs"],
+ rules: {
+ "mozilla/reject-import-system-module-from-non-system": "error",
+ "mozilla/reject-lazy-imports-into-globals": "error",
+ },
+ },
+ {
+ files: ["**/*.mjs"],
+ rules: {
+ "mozilla/use-static-import": "error",
+ // This rule defaults to not allowing "use strict" in module files since
+ // they are always loaded in strict mode.
+ strict: "error",
+ },
+ },
+ {
+ files: ["**/*.jsm", "**/*.jsm.js"],
+ rules: {
+ "mozilla/mark-exported-symbols-as-used": "error",
+ },
+ },
+ {
+ env: {
+ browser: false,
+ "mozilla/privileged": false,
+ "mozilla/sjs": true,
+ },
+ files: ["**/*.sjs"],
+ rules: {
+ // TODO Bug 1501127: sjs files have their own sandbox, and do not inherit
+ // the Window backstage pass directly. Turn this rule off for sjs files for
+ // now until we develop a solution.
+ "mozilla/reject-importGlobalProperties": "off",
+ },
+ },
+ ],
+
+ parserOptions: {
+ ecmaVersion: 12,
+ },
+
+ // When adding items to this file please check for effects on sub-directories.
+ plugins: ["html", "fetch-options", "no-unsanitized"],
+
+ // When adding items to this file please check for effects on all of toolkit
+ // and browser
+ rules: {
+ // Warn about cyclomatic complexity in functions.
+ // XXX Get this down to 20?
+ complexity: ["error", 34],
+
+ // Functions must always return something or nothing
+ "consistent-return": "error",
+
+ // XXX This rule line should be removed to enable it. See bug 1487642.
+ // Require super() calls in constructors
+ "constructor-super": "off",
+
+ // Require braces around blocks that start a new line
+ curly: ["error", "all"],
+
+ // Encourage the use of dot notation whenever possible.
+ "dot-notation": "error",
+
+ // XXX This rule should be enabled, see Bug 1557040
+ // No credentials submitted with fetch calls
+ "fetch-options/no-fetch-credentials": "off",
+
+ // XXX This rule line should be removed to enable it. See bug 1487642.
+ // Enforce return statements in getters
+ "getter-return": "off",
+
+ // Don't enforce the maximum depth that blocks can be nested. The complexity
+ // rule is a better rule to check this.
+ "max-depth": "off",
+
+ // Maximum depth callbacks can be nested.
+ "max-nested-callbacks": ["error", 10],
+
+ "mozilla/avoid-removeChild": "error",
+ "mozilla/consistent-if-bracing": "error",
+ "mozilla/import-browser-window-globals": "error",
+ "mozilla/import-globals": "error",
+ "mozilla/no-compare-against-boolean-literals": "error",
+ "mozilla/no-cu-reportError": "error",
+ "mozilla/no-define-cc-etc": "error",
+ "mozilla/no-throw-cr-literal": "error",
+ "mozilla/no-useless-parameters": "error",
+ "mozilla/no-useless-removeEventListener": "error",
+ "mozilla/prefer-boolean-length-check": "error",
+ "mozilla/prefer-formatValues": "error",
+ "mozilla/reject-addtask-only": "error",
+ "mozilla/reject-chromeutils-import-params": "error",
+ "mozilla/reject-importGlobalProperties": ["error", "allownonwebidl"],
+ "mozilla/reject-multiple-getters-calls": "error",
+ "mozilla/reject-osfile": "warn",
+ "mozilla/reject-scriptableunicodeconverter": "warn",
+ "mozilla/rejects-requires-await": "error",
+ "mozilla/use-cc-etc": "error",
+ "mozilla/use-chromeutils-generateqi": "error",
+ "mozilla/use-chromeutils-import": "error",
+ "mozilla/use-default-preference-values": "error",
+ "mozilla/use-includes-instead-of-indexOf": "error",
+ "mozilla/use-isInstance": "error",
+ "mozilla/use-ownerGlobal": "error",
+ "mozilla/use-returnValue": "error",
+ "mozilla/use-services": "error",
+ "mozilla/valid-lazy": "error",
+ "mozilla/valid-services": "error",
+
+ // Use [] instead of Array()
+ "no-array-constructor": "error",
+
+ // Disallow use of arguments.caller or arguments.callee.
+ "no-caller": "error",
+
+ // XXX Bug 1487642 - decide if we want to enable this or not.
+ // Disallow lexical declarations in case clauses
+ "no-case-declarations": "off",
+
+ // XXX Bug 1487642 - decide if we want to enable this or not.
+ // Disallow the use of console
+ "no-console": "off",
+
+ // Disallows expressions where the operation doesn't affect the value.
+ "no-constant-binary-expression": "error",
+
+ // XXX Bug 1487642 - decide if we want to enable this or not.
+ // Disallow constant expressions in conditions
+ "no-constant-condition": "off",
+
+ // No duplicate keys in object declarations
+ "no-dupe-keys": "error",
+
+ // If an if block ends with a return no need for an else block
+ "no-else-return": "error",
+
+ // No empty statements
+ "no-empty": ["error", { allowEmptyCatch: true }],
+
+ // Disallow eval and setInteral/setTimeout with strings
+ "no-eval": "error",
+
+ // Disallow unnecessary calls to .bind()
+ "no-extra-bind": "error",
+
+ // Disallow fallthrough of case statements
+ "no-fallthrough": [
+ "error",
+ {
+ // The eslint rule doesn't allow for case-insensitive regex option.
+ // The following pattern allows for a dash between "fall through" as
+ // well as alternate spelling of "fall thru". The pattern also allows
+ // for an optional "s" at the end of "fall" ("falls through").
+ commentPattern:
+ "[Ff][Aa][Ll][Ll][Ss]?[\\s-]?([Tt][Hh][Rr][Oo][Uu][Gg][Hh]|[Tt][Hh][Rr][Uu])",
+ },
+ ],
+
+ // Disallow assignments to native objects or read-only global variables
+ "no-global-assign": "error",
+
+ // Disallow eval and setInteral/setTimeout with strings
+ "no-implied-eval": "error",
+
+ // This has been superseded since we're using ES6.
+ // Disallow variable or function declarations in nested blocks
+ "no-inner-declarations": "off",
+
+ // Disallow the use of the __iterator__ property
+ "no-iterator": "error",
+
+ // No labels
+ "no-labels": "error",
+
+ // Disallow unnecessary nested blocks
+ "no-lone-blocks": "error",
+
+ // No single if block inside an else block
+ "no-lonely-if": "error",
+
+ // Disallow the use of number literals that immediately lose precision at runtime when converted to JS Number
+ "no-loss-of-precision": "error",
+
+ // Nested ternary statements are confusing
+ "no-nested-ternary": "error",
+
+ // Use {} instead of new Object()
+ "no-new-object": "error",
+
+ // Disallow use of new wrappers
+ "no-new-wrappers": "error",
+
+ // We don't want this, see bug 1551829
+ "no-prototype-builtins": "off",
+
+ // Disable builtinGlobals for no-redeclare as this conflicts with our
+ // globals declarations especially for browser window.
+ "no-redeclare": ["error", { builtinGlobals: false }],
+
+ // Disallow use of event global.
+ "no-restricted-globals": ["error", "event"],
+
+ // Disallows unnecessary `return await ...`.
+ "no-return-await": "error",
+
+ // No unnecessary comparisons
+ "no-self-compare": "error",
+
+ // No comma sequenced statements
+ "no-sequences": "error",
+
+ // No declaring variables from an outer scope
+ // "no-shadow": "error",
+
+ // No declaring variables that hide things like arguments
+ "no-shadow-restricted-names": "error",
+
+ // Disallow throwing literals (eg. throw "error" instead of
+ // throw new Error("error")).
+ "no-throw-literal": "error",
+
+ // Disallow the use of Boolean literals in conditional expressions.
+ "no-unneeded-ternary": "error",
+
+ // No unsanitized use of innerHTML=, document.write() etc.
+ // cf. https://github.com/mozilla/eslint-plugin-no-unsanitized#rule-details
+ "no-unsanitized/method": "error",
+ "no-unsanitized/property": "error",
+
+ // No declaring variables that are never used
+ "no-unused-vars": [
+ "error",
+ {
+ args: "none",
+ vars: "local",
+ },
+ ],
+
+ // No using variables before defined
+ // "no-use-before-define": ["error", "nofunc"],
+
+ // Disallow unnecessary .call() and .apply()
+ "no-useless-call": "error",
+
+ // Don't concatenate string literals together (unless they span multiple
+ // lines)
+ "no-useless-concat": "error",
+
+ // XXX Bug 1487642 - decide if we want to enable this or not.
+ // Disallow unnecessary escape characters
+ "no-useless-escape": "off",
+
+ // Disallow redundant return statements
+ "no-useless-return": "error",
+
+ // No using with
+ "no-with": "error",
+
+ // Require object-literal shorthand with ES6 method syntax
+ "object-shorthand": ["error", "always", { avoidQuotes: true }],
+
+ // This generates too many false positives that are not easy to work around,
+ // and false positives seem to be inherent in the rule.
+ "require-atomic-updates": "off",
+
+ // XXX Bug 1487642 - decide if we want to enable this or not.
+ // Require generator functions to contain yield
+ "require-yield": "off",
+ },
+
+ // To avoid bad interactions of the html plugin with the xml preprocessor in
+ // eslint-plugin-mozilla, we turn off processing of the html plugin for .xml
+ // files.
+ settings: {
+ "html/xml-extensions": [".xhtml"],
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/require-jsdoc.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/require-jsdoc.js
new file mode 100644
index 0000000000..0a037ab159
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/require-jsdoc.js
@@ -0,0 +1,32 @@
+/* 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/. */
+
+"use strict";
+
+module.exports = {
+ plugins: ["jsdoc"],
+
+ rules: {
+ "jsdoc/require-jsdoc": [
+ "error",
+ {
+ require: {
+ ClassDeclaration: true,
+ FunctionDeclaration: false,
+ },
+ },
+ ],
+ "jsdoc/require-param": "error",
+ "jsdoc/require-param-description": "error",
+ "jsdoc/require-param-name": "error",
+ "jsdoc/require-property": "error",
+ "jsdoc/require-property-description": "error",
+ "jsdoc/require-property-name": "error",
+ "jsdoc/require-property-type": "error",
+ "jsdoc/require-returns": "error",
+ "jsdoc/require-returns-check": "error",
+ "jsdoc/require-yields": "error",
+ "jsdoc/require-yields-check": "error",
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/valid-jsdoc.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/valid-jsdoc.js
new file mode 100644
index 0000000000..485dddfbd4
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/valid-jsdoc.js
@@ -0,0 +1,25 @@
+/* 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/. */
+
+"use strict";
+
+module.exports = {
+ plugins: ["jsdoc"],
+
+ rules: {
+ "jsdoc/check-access": "error",
+ // Handled by prettier
+ // "jsdoc/check-alignment": "error",
+ "jsdoc/check-param-names": "error",
+ "jsdoc/check-property-names": "error",
+ "jsdoc/check-tag-names": "error",
+ "jsdoc/check-types": "error",
+ "jsdoc/empty-tags": "error",
+ "jsdoc/newline-after-description": "error",
+ "jsdoc/no-multi-asterisks": "error",
+ "jsdoc/require-param-type": "error",
+ "jsdoc/require-returns-type": "error",
+ "jsdoc/valid-types": "error",
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/xpcshell-test.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/xpcshell-test.js
new file mode 100644
index 0000000000..492ecc1ae9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/configs/xpcshell-test.js
@@ -0,0 +1,50 @@
+// Parent config file for all xpcshell files.
+"use strict";
+
+module.exports = {
+ env: {
+ browser: false,
+ "mozilla/privileged": true,
+ "mozilla/xpcshell": true,
+ },
+
+ overrides: [
+ {
+ // If it is a head file, we turn off global unused variable checks, as it
+ // would require searching the other test files to know if they are used or not.
+ // This would be expensive and slow, and it isn't worth it for head files.
+ // We could get developers to declare as exported, but that doesn't seem worth it.
+ files: "head*.js",
+ rules: {
+ "no-unused-vars": [
+ "error",
+ {
+ args: "none",
+ vars: "local",
+ },
+ ],
+ },
+ },
+ {
+ // No declaring variables that are never used
+ files: "test*.js",
+ rules: {
+ "no-unused-vars": [
+ "error",
+ {
+ args: "none",
+ vars: "all",
+ },
+ ],
+ },
+ },
+ ],
+
+ rules: {
+ "mozilla/import-headjs-globals": "error",
+ "mozilla/mark-test-function-used": "error",
+ "mozilla/no-arbitrary-setTimeout": "error",
+ "mozilla/no-useless-run-test": "error",
+ "no-shadow": "error",
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
new file mode 100644
index 0000000000..6b40dedd45
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/browser-window.js
@@ -0,0 +1,118 @@
+/**
+ * @fileoverview Defines the environment when in the browser.xhtml window.
+ * Imports many globals from various files.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var fs = require("fs");
+var helpers = require("../helpers");
+var { getScriptGlobals } = require("./utils");
+
+// When updating EXTRA_SCRIPTS or MAPPINGS, be sure to also update the
+// 'support-files' config in `tools/lint/eslint.yml`.
+
+// These are scripts not loaded from browser.xhtml or global-scripts.inc
+// but via other includes.
+const EXTRA_SCRIPTS = [
+ "browser/base/content/nsContextMenu.js",
+ "browser/components/downloads/content/downloads.js",
+ "browser/components/downloads/content/indicator.js",
+ "toolkit/content/customElements.js",
+ "toolkit/content/editMenuOverlay.js",
+];
+
+const extraDefinitions = [
+ // Via Components.utils, defineModuleGetter, defineLazyModuleGetters or
+ // defineLazyScriptGetter (and map to
+ // single) variable.
+ { name: "XPCOMUtils", writable: false },
+ { name: "Task", writable: false },
+ { name: "windowGlobalChild", writable: false },
+ // structuredClone is a new global that would be defined for the `browser`
+ // environment in ESLint, but only Firefox has implemented it currently and so
+ // it isn't in ESLint's globals yet.
+ // https://developer.mozilla.org/docs/Web/API/structuredClone
+ { name: "structuredClone", writable: false },
+];
+
+// Some files in global-scripts.inc need mapping to specific locations.
+const MAPPINGS = {
+ "printUtils.js": "toolkit/components/printing/content/printUtils.js",
+ "panelUI.js": "browser/components/customizableui/content/panelUI.js",
+ "viewSourceUtils.js":
+ "toolkit/components/viewsource/content/viewSourceUtils.js",
+ "browserPlacesViews.js":
+ "browser/components/places/content/browserPlacesViews.js",
+ "places-tree.js": "browser/components/places/content/places-tree.js",
+ "places-menupopup.js":
+ "browser/components/places/content/places-menupopup.js",
+};
+
+const globalScriptsRegExp = /^\s*Services.scriptloader.loadSubScript\(\"(.*?)\", this\);$/;
+
+function getGlobalScriptIncludes(scriptPath) {
+ let fileData;
+ try {
+ fileData = fs.readFileSync(scriptPath, { encoding: "utf8" });
+ } catch (ex) {
+ // The file isn't present, so this isn't an m-c repository.
+ return null;
+ }
+
+ fileData = fileData.split("\n");
+
+ let result = [];
+
+ for (let line of fileData) {
+ let match = line.match(globalScriptsRegExp);
+ if (match) {
+ let sourceFile = match[1]
+ .replace(
+ "chrome://browser/content/search/",
+ "browser/components/search/content/"
+ )
+ .replace(
+ "chrome://browser/content/screenshots/",
+ "browser/components/screenshots/content/"
+ )
+ .replace("chrome://browser/content/", "browser/base/content/")
+ .replace("chrome://global/content/", "toolkit/content/");
+
+ for (let mapping of Object.getOwnPropertyNames(MAPPINGS)) {
+ if (sourceFile.includes(mapping)) {
+ sourceFile = MAPPINGS[mapping];
+ }
+ }
+
+ result.push(sourceFile);
+ }
+ }
+
+ return result;
+}
+
+function getGlobalScripts() {
+ let results = [];
+ for (let scriptPath of helpers.globalScriptPaths) {
+ results = results.concat(getGlobalScriptIncludes(scriptPath));
+ }
+ return results;
+}
+
+module.exports = getScriptGlobals(
+ "browser-window",
+ getGlobalScripts().concat(EXTRA_SCRIPTS),
+ extraDefinitions,
+ {
+ browserjsScripts: getGlobalScripts().concat(EXTRA_SCRIPTS),
+ }
+);
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-script.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-script.js
new file mode 100644
index 0000000000..9b0ae54a2e
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-script.js
@@ -0,0 +1,28 @@
+/**
+ * @fileoverview Defines the environment for SpecialPowers chrome script.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var { globals } = require("./special-powers-sandbox");
+var util = require("util");
+
+module.exports = {
+ globals: util._extend(
+ {
+ // testing/specialpowers/content/SpecialPowersParent.sys.mjs
+
+ // SPLoadChromeScript block
+ createWindowlessBrowser: false,
+ sendAsyncMessage: false,
+ addMessageListener: false,
+ removeMessageListener: false,
+ actorParent: false,
+ },
+ globals
+ ),
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-worker.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-worker.js
new file mode 100644
index 0000000000..db5759b26c
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/chrome-worker.js
@@ -0,0 +1,25 @@
+/**
+ * @fileoverview Defines the environment for chrome workers. This differs
+ * from normal workers by the fact that `ctypes` can be accessed
+ * as well.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var globals = require("globals");
+var util = require("util");
+
+var workerGlobals = util._extend(
+ {
+ ctypes: false,
+ },
+ globals.worker
+);
+
+module.exports = {
+ globals: workerGlobals,
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
new file mode 100644
index 0000000000..7ac5c941cf
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/frame-script.js
@@ -0,0 +1,39 @@
+/**
+ * @fileoverview Defines the environment for frame scripts.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ // dom/chrome-webidl/MessageManager.webidl
+
+ // MessageManagerGlobal
+ dump: false,
+ atob: false,
+ btoa: false,
+
+ // MessageListenerManagerMixin
+ addMessageListener: false,
+ removeMessageListener: false,
+ addWeakMessageListener: false,
+ removeWeakMessageListener: false,
+
+ // MessageSenderMixin
+ sendAsyncMessage: false,
+ processMessageManager: false,
+ remoteType: false,
+
+ // SyncMessageSenderMixin
+ sendSyncMessage: false,
+
+ // ContentFrameMessageManager
+ content: false,
+ docShell: false,
+ tabEventTarget: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/jsm.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/jsm.js
new file mode 100644
index 0000000000..1ff73c04d9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/jsm.js
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview Defines the environment for jsm files.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ // These globals are hard-coded and available in .jsm scopes.
+ // https://searchfox.org/mozilla-central/rev/dcb0cfb66e4ed3b9c7fbef1e80572426ff5f3c3a/js/xpconnect/loader/mozJSModuleLoader.cpp#222-223
+ // Although `debug` is allowed for jsm files, this is non-standard and something
+ // we don't want to allow in mjs files. Hence it is not included here.
+ atob: false,
+ btoa: false,
+ dump: false,
+ // The WebAssembly global is available in most (if not all) contexts where
+ // JS can run. It's definitely available in JSMs. So even if this is not
+ // the perfect place to add it, it's not wrong, and we can move it later.
+ WebAssembly: false,
+ // These are hard-coded and available in .jsm scopes.
+ // See BackstagePass::Resolve.
+ fetch: false,
+ crypto: false,
+ indexedDB: false,
+ structuredClone: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/privileged.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/privileged.js
new file mode 100644
index 0000000000..89162cdb2f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/privileged.js
@@ -0,0 +1,805 @@
+/**
+ * @fileoverview Defines the environment for privileges JS files.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ // Intl and WebAssembly are available everywhere but are not webIDL definitions.
+ Intl: false,
+ WebAssembly: false,
+ // This list of items is currently obtained manually from the list of
+ // mozilla::dom::constructor::id::ID enumerations in an object directory
+ // generated dom/bindings/RegisterBindings.cpp
+ APZHitResultFlags: false,
+ AbortController: false,
+ AbortSignal: false,
+ AccessibleNode: false,
+ Addon: false,
+ AddonEvent: false,
+ AddonInstall: false,
+ AddonManager: true,
+ AddonManagerPermissions: false,
+ AnalyserNode: false,
+ Animation: false,
+ AnimationEffect: false,
+ AnimationEvent: false,
+ AnimationPlaybackEvent: false,
+ AnimationTimeline: false,
+ AnonymousContent: false,
+ Attr: false,
+ AudioBuffer: false,
+ AudioBufferSourceNode: false,
+ AudioContext: false,
+ AudioDestinationNode: false,
+ AudioListener: false,
+ AudioNode: false,
+ AudioParam: false,
+ AudioParamMap: false,
+ AudioProcessingEvent: false,
+ AudioScheduledSourceNode: false,
+ AudioTrack: false,
+ AudioTrackList: false,
+ AudioWorklet: false,
+ AudioWorkletNode: false,
+ AuthenticatorAssertionResponse: false,
+ AuthenticatorAttestationResponse: false,
+ AuthenticatorResponse: false,
+ BarProp: false,
+ BaseAudioContext: false,
+ BatteryManager: false,
+ BeforeUnloadEvent: false,
+ BiquadFilterNode: false,
+ Blob: false,
+ BlobEvent: false,
+ BoxObject: false,
+ BroadcastChannel: false,
+ BrowsingContext: false,
+ ByteLengthQueuingStrategy: false,
+ CanonicalBrowsingContext: false,
+ CDATASection: false,
+ CSS: false,
+ CSS2Properties: false,
+ CSSAnimation: false,
+ CSSConditionRule: false,
+ CSSCounterStyleRule: false,
+ CSSFontFaceRule: false,
+ CSSFontFeatureValuesRule: false,
+ CSSGroupingRule: false,
+ CSSImportRule: false,
+ CSSKeyframeRule: false,
+ CSSKeyframesRule: false,
+ CSSMediaRule: false,
+ CSSMozDocumentRule: false,
+ CSSNamespaceRule: false,
+ CSSPageRule: false,
+ CSSPseudoElement: false,
+ CSSRule: false,
+ CSSRuleList: false,
+ CSSStyleDeclaration: false,
+ CSSStyleRule: false,
+ CSSStyleSheet: false,
+ CSSSupportsRule: false,
+ CSSTransition: false,
+ Cache: false,
+ CacheStorage: false,
+ CanvasCaptureMediaStream: false,
+ CanvasGradient: false,
+ CanvasPattern: false,
+ CanvasRenderingContext2D: false,
+ CaretPosition: false,
+ CaretStateChangedEvent: false,
+ ChannelMergerNode: false,
+ ChannelSplitterNode: false,
+ ChannelWrapper: false,
+ CharacterData: false,
+ CheckerboardReportService: false,
+ ChildProcessMessageManager: false,
+ ChildSHistory: false,
+ ChromeMessageBroadcaster: false,
+ ChromeMessageSender: false,
+ ChromeNodeList: false,
+ ChromeUtils: false,
+ ChromeWorker: false,
+ Clipboard: false,
+ ClipboardEvent: false,
+ ClonedErrorHolder: false,
+ CloseEvent: false,
+ CommandEvent: false,
+ Comment: false,
+ CompositionEvent: false,
+ ConsoleInstance: false,
+ ConstantSourceNode: false,
+ ContentFrameMessageManager: false,
+ ContentProcessMessageManager: false,
+ ConvolverNode: false,
+ CountQueuingStrategy: false,
+ CreateOfferRequest: false,
+ Credential: false,
+ CredentialsContainer: false,
+ Crypto: false,
+ CryptoKey: false,
+ CustomElementRegistry: false,
+ CustomEvent: false,
+ DOMError: false,
+ DOMException: false,
+ DOMImplementation: false,
+ DOMLocalization: false,
+ DOMMatrix: false,
+ DOMMatrixReadOnly: false,
+ DOMParser: false,
+ DOMPoint: false,
+ DOMPointReadOnly: false,
+ DOMQuad: false,
+ DOMRect: false,
+ DOMRectList: false,
+ DOMRectReadOnly: false,
+ DOMRequest: false,
+ DOMStringList: false,
+ DOMStringMap: false,
+ DOMTokenList: false,
+ DataTransfer: false,
+ DataTransferItem: false,
+ DataTransferItemList: false,
+ DebuggerNotificationObserver: false,
+ DelayNode: false,
+ DeprecationReportBody: false,
+ DeviceLightEvent: false,
+ DeviceMotionEvent: false,
+ DeviceOrientationEvent: false,
+ DeviceProximityEvent: false,
+ Directory: false,
+ Document: false,
+ DocumentFragment: false,
+ DocumentTimeline: false,
+ DocumentType: false,
+ DominatorTree: false,
+ DragEvent: false,
+ DynamicsCompressorNode: false,
+ Element: false,
+ ErrorEvent: false,
+ Event: false,
+ EventSource: false,
+ EventTarget: false,
+ FeaturePolicyViolationReportBody: false,
+ FetchObserver: false,
+ File: false,
+ FileList: false,
+ FileReader: false,
+ FileSystem: false,
+ FileSystemDirectoryEntry: false,
+ FileSystemDirectoryReader: false,
+ FileSystemEntry: false,
+ FileSystemFileEntry: false,
+ Flex: false,
+ FlexItemValues: false,
+ FlexLineValues: false,
+ FluentBundle: false,
+ FluentResource: false,
+ FocusEvent: false,
+ FontFace: false,
+ FontFaceSet: false,
+ FontFaceSetLoadEvent: false,
+ FormData: false,
+ FrameCrashedEvent: false,
+ FrameLoader: false,
+ GainNode: false,
+ Gamepad: false,
+ GamepadAxisMoveEvent: false,
+ GamepadButton: false,
+ GamepadButtonEvent: false,
+ GamepadEvent: false,
+ GamepadHapticActuator: false,
+ GamepadPose: false,
+ GamepadServiceTest: false,
+ Glean: false,
+ GleanPings: false,
+ Grid: false,
+ GridArea: false,
+ GridDimension: false,
+ GridLine: false,
+ GridLines: false,
+ GridTrack: false,
+ GridTracks: false,
+ HTMLAllCollection: false,
+ HTMLAnchorElement: false,
+ HTMLAreaElement: false,
+ HTMLAudioElement: false,
+ Audio: false,
+ HTMLBRElement: false,
+ HTMLBaseElement: false,
+ HTMLBodyElement: false,
+ HTMLButtonElement: false,
+ HTMLCanvasElement: false,
+ HTMLCollection: false,
+ HTMLDListElement: false,
+ HTMLDataElement: false,
+ HTMLDataListElement: false,
+ HTMLDetailsElement: false,
+ HTMLDialogElement: false,
+ HTMLDirectoryElement: false,
+ HTMLDivElement: false,
+ HTMLDocument: false,
+ HTMLElement: false,
+ HTMLEmbedElement: false,
+ HTMLFieldSetElement: false,
+ HTMLFontElement: false,
+ HTMLFormControlsCollection: false,
+ HTMLFormElement: false,
+ HTMLFrameElement: false,
+ HTMLFrameSetElement: false,
+ HTMLHRElement: false,
+ HTMLHeadElement: false,
+ HTMLHeadingElement: false,
+ HTMLHtmlElement: false,
+ HTMLIFrameElement: false,
+ HTMLImageElement: false,
+ Image: false,
+ HTMLInputElement: false,
+ HTMLLIElement: false,
+ HTMLLabelElement: false,
+ HTMLLegendElement: false,
+ HTMLLinkElement: false,
+ HTMLMapElement: false,
+ HTMLMarqueeElement: false,
+ HTMLMediaElement: false,
+ HTMLMenuElement: false,
+ HTMLMenuItemElement: false,
+ HTMLMetaElement: false,
+ HTMLMeterElement: false,
+ HTMLModElement: false,
+ HTMLOListElement: false,
+ HTMLObjectElement: false,
+ HTMLOptGroupElement: false,
+ HTMLOptionElement: false,
+ Option: false,
+ HTMLOptionsCollection: false,
+ HTMLOutputElement: false,
+ HTMLParagraphElement: false,
+ HTMLParamElement: false,
+ HTMLPictureElement: false,
+ HTMLPreElement: false,
+ HTMLProgressElement: false,
+ HTMLQuoteElement: false,
+ HTMLScriptElement: false,
+ HTMLSelectElement: false,
+ HTMLSlotElement: false,
+ HTMLSourceElement: false,
+ HTMLSpanElement: false,
+ HTMLStyleElement: false,
+ HTMLTableCaptionElement: false,
+ HTMLTableCellElement: false,
+ HTMLTableColElement: false,
+ HTMLTableElement: false,
+ HTMLTableRowElement: false,
+ HTMLTableSectionElement: false,
+ HTMLTemplateElement: false,
+ HTMLTextAreaElement: false,
+ HTMLTimeElement: false,
+ HTMLTitleElement: false,
+ HTMLTrackElement: false,
+ HTMLUListElement: false,
+ HTMLUnknownElement: false,
+ HTMLVideoElement: false,
+ HashChangeEvent: false,
+ Headers: false,
+ HeapSnapshot: false,
+ History: false,
+ IDBCursor: false,
+ IDBCursorWithValue: false,
+ IDBDatabase: false,
+ IDBFactory: false,
+ IDBFileHandle: false,
+ IDBFileRequest: false,
+ IDBIndex: false,
+ IDBKeyRange: false,
+ IDBLocaleAwareKeyRange: false,
+ IDBMutableFile: false,
+ IDBObjectStore: false,
+ IDBOpenDBRequest: false,
+ IDBRequest: false,
+ IDBTransaction: false,
+ IDBVersionChangeEvent: false,
+ IIRFilterNode: false,
+ IdleDeadline: false,
+ ImageBitmap: false,
+ ImageBitmapRenderingContext: false,
+ ImageCapture: false,
+ ImageCaptureErrorEvent: false,
+ ImageData: false,
+ ImageDocument: false,
+ InputEvent: false,
+ InspectorFontFace: false,
+ InspectorUtils: false,
+ InstallTriggerImpl: false,
+ IntersectionObserver: false,
+ IntersectionObserverEntry: false,
+ IOUtils: false,
+ JSProcessActorChild: false,
+ JSProcessActorParent: false,
+ JSWindowActorChild: false,
+ JSWindowActorParent: false,
+ KeyEvent: false,
+ KeyboardEvent: false,
+ KeyframeEffect: false,
+ L10nFileSource: false,
+ L10nRegistry: false,
+ Localization: false,
+ Location: false,
+ MIDIAccess: false,
+ MIDIConnectionEvent: false,
+ MIDIInput: false,
+ MIDIInputMap: false,
+ MIDIMessageEvent: false,
+ MIDIOutput: false,
+ MIDIOutputMap: false,
+ MIDIPort: false,
+ MatchGlob: false,
+ MatchPattern: false,
+ MatchPatternSet: false,
+ MediaCapabilities: false,
+ MediaCapabilitiesInfo: false,
+ MediaControlService: false,
+ MediaDeviceInfo: false,
+ MediaDevices: false,
+ MediaElementAudioSourceNode: false,
+ MediaEncryptedEvent: false,
+ MediaError: false,
+ MediaKeyError: false,
+ MediaKeyMessageEvent: false,
+ MediaKeySession: false,
+ MediaKeyStatusMap: false,
+ MediaKeySystemAccess: false,
+ MediaKeys: false,
+ MediaList: false,
+ MediaQueryList: false,
+ MediaQueryListEvent: false,
+ MediaRecorder: false,
+ MediaRecorderErrorEvent: false,
+ MediaSource: false,
+ MediaStream: false,
+ MediaStreamAudioDestinationNode: false,
+ MediaStreamAudioSourceNode: false,
+ MediaStreamEvent: false,
+ MediaStreamTrack: false,
+ MediaStreamTrackEvent: false,
+ MerchantValidationEvent: false,
+ MessageBroadcaster: false,
+ MessageChannel: false,
+ MessageEvent: false,
+ MessageListenerManager: false,
+ MessagePort: false,
+ MessageSender: false,
+ MimeType: false,
+ MimeTypeArray: false,
+ MouseEvent: false,
+ MouseScrollEvent: false,
+ MozCanvasPrintState: false,
+ MozDocumentMatcher: false,
+ MozDocumentObserver: false,
+ MozQueryInterface: false,
+ MozSharedMap: false,
+ MozSharedMapChangeEvent: false,
+ MozStorageAsyncStatementParams: false,
+ MozStorageStatementParams: false,
+ MozStorageStatementRow: false,
+ MozWritableSharedMap: false,
+ MutationEvent: false,
+ MutationObserver: false,
+ MutationRecord: false,
+ NamedNodeMap: false,
+ Navigator: false,
+ NetworkInformation: false,
+ Node: false,
+ NodeFilter: false,
+ NodeIterator: false,
+ NodeList: false,
+ Notification: false,
+ NotifyPaintEvent: false,
+ OfflineAudioCompletionEvent: false,
+ OfflineAudioContext: false,
+ OfflineResourceList: false,
+ OffscreenCanvas: false,
+ OscillatorNode: false,
+ PageTransitionEvent: false,
+ PaintRequest: false,
+ PaintRequestList: false,
+ PannerNode: false,
+ ParentProcessMessageManager: false,
+ Path2D: false,
+ PathUtils: false,
+ PaymentAddress: false,
+ PaymentMethodChangeEvent: false,
+ PaymentRequest: false,
+ PaymentRequestUpdateEvent: false,
+ PaymentResponse: false,
+ PeerConnectionImpl: false,
+ PeerConnectionObserver: false,
+ Performance: false,
+ PerformanceEntry: false,
+ PerformanceEntryEvent: false,
+ PerformanceMark: false,
+ PerformanceMeasure: false,
+ PerformanceNavigation: false,
+ PerformanceNavigationTiming: false,
+ PerformanceObserver: false,
+ PerformanceObserverEntryList: false,
+ PerformanceResourceTiming: false,
+ PerformanceServerTiming: false,
+ PerformanceTiming: false,
+ PeriodicWave: false,
+ PermissionStatus: false,
+ Permissions: false,
+ PlacesBookmark: false,
+ PlacesBookmarkAddition: false,
+ PlacesBookmarkGuid: false,
+ PlacesBookmarkMoved: false,
+ PlacesBookmarkRemoved: false,
+ PlacesBookmarkTags: false,
+ PlacesBookmarkTime: false,
+ PlacesBookmarkTitle: false,
+ PlacesBookmarkUrl: false,
+ PlacesEvent: false,
+ PlacesHistoryCleared: false,
+ PlacesObservers: false,
+ PlacesPurgeCaches: false,
+ PlacesRanking: false,
+ PlacesVisit: false,
+ PlacesVisitRemoved: false,
+ PlacesVisitTitle: false,
+ PlacesWeakCallbackWrapper: false,
+ Plugin: false,
+ PluginArray: false,
+ PluginCrashedEvent: false,
+ PointerEvent: false,
+ PopStateEvent: false,
+ PopupBlockedEvent: false,
+ PrecompiledScript: false,
+ Presentation: false,
+ PresentationAvailability: false,
+ PresentationConnection: false,
+ PresentationConnectionAvailableEvent: false,
+ PresentationConnectionCloseEvent: false,
+ PresentationConnectionList: false,
+ PresentationReceiver: false,
+ PresentationRequest: false,
+ PrioEncoder: false,
+ ProcessMessageManager: false,
+ ProcessingInstruction: false,
+ ProgressEvent: false,
+ PromiseDebugging: false,
+ PromiseRejectionEvent: false,
+ PublicKeyCredential: false,
+ PushManager: false,
+ PushManagerImpl: false,
+ PushSubscription: false,
+ PushSubscriptionOptions: false,
+ RTCCertificate: false,
+ RTCDTMFSender: false,
+ RTCDTMFToneChangeEvent: false,
+ RTCDataChannel: false,
+ RTCDataChannelEvent: false,
+ RTCIceCandidate: false,
+ RTCPeerConnection: false,
+ RTCPeerConnectionIceEvent: false,
+ RTCPeerConnectionStatic: false,
+ RTCRtpReceiver: false,
+ RTCRtpSender: false,
+ RTCRtpTransceiver: false,
+ RTCSessionDescription: false,
+ RTCStatsReport: false,
+ RTCTrackEvent: false,
+ RadioNodeList: false,
+ Range: false,
+ ReadableStreamBYOBReader: false,
+ ReadableStreamBYOBRequest: false,
+ ReadableByteStreamController: false,
+ ReadableStream: false,
+ ReadableStreamDefaultController: false,
+ ReadableStreamDefaultReader: false,
+ Report: false,
+ ReportBody: false,
+ ReportingObserver: false,
+ Request: false,
+ Response: false,
+ SessionStoreUtils: false,
+ SVGAElement: false,
+ SVGAngle: false,
+ SVGAnimateElement: false,
+ SVGAnimateMotionElement: false,
+ SVGAnimateTransformElement: false,
+ SVGAnimatedAngle: false,
+ SVGAnimatedBoolean: false,
+ SVGAnimatedEnumeration: false,
+ SVGAnimatedInteger: false,
+ SVGAnimatedLength: false,
+ SVGAnimatedLengthList: false,
+ SVGAnimatedNumber: false,
+ SVGAnimatedNumberList: false,
+ SVGAnimatedPreserveAspectRatio: false,
+ SVGAnimatedRect: false,
+ SVGAnimatedString: false,
+ SVGAnimatedTransformList: false,
+ SVGAnimationElement: false,
+ SVGCircleElement: false,
+ SVGClipPathElement: false,
+ SVGComponentTransferFunctionElement: false,
+ SVGDefsElement: false,
+ SVGDescElement: false,
+ SVGElement: false,
+ SVGEllipseElement: false,
+ SVGFEBlendElement: false,
+ SVGFEColorMatrixElement: false,
+ SVGFEComponentTransferElement: false,
+ SVGFECompositeElement: false,
+ SVGFEConvolveMatrixElement: false,
+ SVGFEDiffuseLightingElement: false,
+ SVGFEDisplacementMapElement: false,
+ SVGFEDistantLightElement: false,
+ SVGFEDropShadowElement: false,
+ SVGFEFloodElement: false,
+ SVGFEFuncAElement: false,
+ SVGFEFuncBElement: false,
+ SVGFEFuncGElement: false,
+ SVGFEFuncRElement: false,
+ SVGFEGaussianBlurElement: false,
+ SVGFEImageElement: false,
+ SVGFEMergeElement: false,
+ SVGFEMergeNodeElement: false,
+ SVGFEMorphologyElement: false,
+ SVGFEOffsetElement: false,
+ SVGFEPointLightElement: false,
+ SVGFESpecularLightingElement: false,
+ SVGFESpotLightElement: false,
+ SVGFETileElement: false,
+ SVGFETurbulenceElement: false,
+ SVGFilterElement: false,
+ SVGForeignObjectElement: false,
+ SVGGElement: false,
+ SVGGeometryElement: false,
+ SVGGradientElement: false,
+ SVGGraphicsElement: false,
+ SVGImageElement: false,
+ SVGLength: false,
+ SVGLengthList: false,
+ SVGLineElement: false,
+ SVGLinearGradientElement: false,
+ SVGMPathElement: false,
+ SVGMarkerElement: false,
+ SVGMaskElement: false,
+ SVGMatrix: false,
+ SVGMetadataElement: false,
+ SVGNumber: false,
+ SVGNumberList: false,
+ SVGPathElement: false,
+ SVGPathSegList: false,
+ SVGPatternElement: false,
+ SVGPoint: false,
+ SVGPointList: false,
+ SVGPolygonElement: false,
+ SVGPolylineElement: false,
+ SVGPreserveAspectRatio: false,
+ SVGRadialGradientElement: false,
+ SVGRect: false,
+ SVGRectElement: false,
+ SVGSVGElement: false,
+ SVGScriptElement: false,
+ SVGSetElement: false,
+ SVGStopElement: false,
+ SVGStringList: false,
+ SVGStyleElement: false,
+ SVGSwitchElement: false,
+ SVGSymbolElement: false,
+ SVGTSpanElement: false,
+ SVGTextContentElement: false,
+ SVGTextElement: false,
+ SVGTextPathElement: false,
+ SVGTextPositioningElement: false,
+ SVGTitleElement: false,
+ SVGTransform: false,
+ SVGTransformList: false,
+ SVGUnitTypes: false,
+ SVGUseElement: false,
+ SVGViewElement: false,
+ SVGZoomAndPan: false,
+ Screen: false,
+ ScreenLuminance: false,
+ ScreenOrientation: false,
+ ScriptProcessorNode: false,
+ ScrollAreaEvent: false,
+ ScrollViewChangeEvent: false,
+ SecurityPolicyViolationEvent: false,
+ Selection: false,
+ ServiceWorker: false,
+ ServiceWorkerContainer: false,
+ ServiceWorkerRegistration: false,
+ ShadowRoot: false,
+ SharedWorker: false,
+ SimpleGestureEvent: false,
+ SourceBuffer: false,
+ SourceBufferList: false,
+ SpeechGrammar: false,
+ SpeechGrammarList: false,
+ SpeechRecognition: false,
+ SpeechRecognitionAlternative: false,
+ SpeechRecognitionError: false,
+ SpeechRecognitionEvent: false,
+ SpeechRecognitionResult: false,
+ SpeechRecognitionResultList: false,
+ SpeechSynthesis: false,
+ SpeechSynthesisErrorEvent: false,
+ SpeechSynthesisEvent: false,
+ SpeechSynthesisUtterance: false,
+ SpeechSynthesisVoice: false,
+ StereoPannerNode: false,
+ Storage: false,
+ StorageEvent: false,
+ StorageManager: false,
+ StreamFilter: false,
+ StreamFilterDataEvent: false,
+ StructuredCloneHolder: false,
+ StructuredCloneTester: false,
+ StyleSheet: false,
+ StyleSheetApplicableStateChangeEvent: false,
+ StyleSheetList: false,
+ SubtleCrypto: false,
+ SyncMessageSender: false,
+ TCPServerSocket: false,
+ TCPServerSocketEvent: false,
+ TCPSocket: false,
+ TCPSocketErrorEvent: false,
+ TCPSocketEvent: false,
+ TelemetryStopwatch: false,
+ TestingDeprecatedInterface: false,
+ Text: false,
+ TextClause: false,
+ TextDecoder: false,
+ TextEncoder: false,
+ TextMetrics: false,
+ TextTrack: false,
+ TextTrackCue: false,
+ TextTrackCueList: false,
+ TextTrackList: false,
+ TimeEvent: false,
+ TimeRanges: false,
+ Touch: false,
+ TouchEvent: false,
+ TouchList: false,
+ TrackEvent: false,
+ TransceiverImpl: false,
+ TransformStream: false,
+ TransformStreamDefaultController: false,
+ TransitionEvent: false,
+ TreeColumn: false,
+ TreeColumns: false,
+ TreeContentView: false,
+ TreeWalker: false,
+ U2F: false,
+ UDPMessageEvent: false,
+ UDPSocket: false,
+ UIEvent: false,
+ URL: false,
+ URLSearchParams: false,
+ UserInteraction: false,
+ UserProximityEvent: false,
+ VRDisplay: false,
+ VRDisplayCapabilities: false,
+ VRDisplayEvent: false,
+ VREyeParameters: false,
+ VRFieldOfView: false,
+ VRFrameData: false,
+ VRMockController: false,
+ VRMockDisplay: false,
+ VRPose: false,
+ VRServiceTest: false,
+ VRStageParameters: false,
+ VRSubmitFrameResult: false,
+ VTTCue: false,
+ VTTRegion: false,
+ ValidityState: false,
+ VideoPlaybackQuality: false,
+ VideoTrack: false,
+ VideoTrackList: false,
+ VisualViewport: false,
+ WaveShaperNode: false,
+ WebExtensionContentScript: false,
+ WebExtensionPolicy: false,
+ WebGL2RenderingContext: false,
+ WebGLActiveInfo: false,
+ WebGLBuffer: false,
+ WebGLContextEvent: false,
+ WebGLFramebuffer: false,
+ WebGLProgram: false,
+ WebGLQuery: false,
+ WebGLRenderbuffer: false,
+ WebGLRenderingContext: false,
+ WebGLSampler: false,
+ WebGLShader: false,
+ WebGLShaderPrecisionFormat: false,
+ WebGLSync: false,
+ WebGLTexture: false,
+ WebGLTransformFeedback: false,
+ WebGLUniformLocation: false,
+ WebGLVertexArrayObject: false,
+ WebGPU: false,
+ WebGPUAdapter: false,
+ WebGPUAttachmentState: false,
+ WebGPUBindGroup: false,
+ WebGPUBindGroupLayout: false,
+ WebGPUBindingType: false,
+ WebGPUBlendFactor: false,
+ WebGPUBlendOperation: false,
+ WebGPUBlendState: false,
+ WebGPUBuffer: false,
+ WebGPUBufferUsage: false,
+ WebGPUColorWriteBits: false,
+ WebGPUCommandBuffer: false,
+ WebGPUCommandEncoder: false,
+ WebGPUCompareFunction: false,
+ WebGPUComputePipeline: false,
+ WebGPUDepthStencilState: false,
+ WebGPUDevice: false,
+ WebGPUFence: false,
+ WebGPUFilterMode: false,
+ WebGPUIndexFormat: false,
+ WebGPUInputState: false,
+ WebGPUInputStepMode: false,
+ WebGPULoadOp: false,
+ WebGPULogEntry: false,
+ WebGPUPipelineLayout: false,
+ WebGPUPrimitiveTopology: false,
+ WebGPUQueue: false,
+ WebGPURenderPipeline: false,
+ WebGPUSampler: false,
+ WebGPUShaderModule: false,
+ WebGPUShaderStage: false,
+ WebGPUShaderStageBit: false,
+ WebGPUStencilOperation: false,
+ WebGPUStoreOp: false,
+ WebGPUSwapChain: false,
+ WebGPUTexture: false,
+ WebGPUTextureDimension: false,
+ WebGPUTextureFormat: false,
+ WebGPUTextureUsage: false,
+ WebGPUTextureView: false,
+ WebGPUVertexFormat: false,
+ WebKitCSSMatrix: false,
+ WebSocket: false,
+ WebrtcGlobalInformation: false,
+ WheelEvent: false,
+ Window: false,
+ WindowGlobalChild: false,
+ WindowGlobalParent: false,
+ WindowRoot: false,
+ Worker: false,
+ Worklet: false,
+ WritableStream: false,
+ WritableStreamDefaultController: false,
+ WritableStreamDefaultWriter: false,
+ XMLDocument: false,
+ XMLHttpRequest: false,
+ XMLHttpRequestEventTarget: false,
+ XMLHttpRequestUpload: false,
+ XMLSerializer: false,
+ XPathEvaluator: false,
+ XPathExpression: false,
+ XPathResult: false,
+ XSLTProcessor: false,
+ XULCommandEvent: false,
+ XULElement: false,
+ XULFrameElement: false,
+ XULMenuElement: false,
+ XULPopupElement: false,
+ XULScrollElement: false,
+ XULTextElement: false,
+ console: false,
+ mozRTCIceCandidate: false,
+ mozRTCPeerConnection: false,
+ mozRTCSessionDescription: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/process-script.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/process-script.js
new file mode 100644
index 0000000000..f329a6650b
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/process-script.js
@@ -0,0 +1,38 @@
+/**
+ * @fileoverview Defines the environment for process scripts.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ // dom/chrome-webidl/MessageManager.webidl
+
+ // MessageManagerGlobal
+ dump: false,
+ atob: false,
+ btoa: false,
+
+ // MessageListenerManagerMixin
+ addMessageListener: false,
+ removeMessageListener: false,
+ addWeakMessageListener: false,
+ removeWeakMessageListener: false,
+
+ // MessageSenderMixin
+ sendAsyncMessage: false,
+ processMessageManager: false,
+ remoteType: false,
+
+ // SyncMessageSenderMixin
+ sendSyncMessage: false,
+
+ // ContentProcessMessageManager
+ initialProcessData: false,
+ sharedData: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/remote-page.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/remote-page.js
new file mode 100644
index 0000000000..0adebe90bc
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/remote-page.js
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Defines the environment for remote page.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ atob: false,
+ btoa: false,
+ RPMAddTRRExcludedDomain: false,
+ RPMGetAppBuildID: false,
+ RPMGetInnerMostURI: false,
+ RPMGetIntPref: false,
+ RPMGetStringPref: false,
+ RPMGetBoolPref: false,
+ RPMSetBoolPref: false,
+ RPMGetFormatURLPref: false,
+ RPMIsTRROnlyFailure: false,
+ RPMShowTRROnlyFailureError: false,
+ RPMIsWindowPrivate: false,
+ RPMSendAsyncMessage: false,
+ RPMSendQuery: false,
+ RPMAddMessageListener: false,
+ RPMRecordTelemetryEvent: false,
+ RPMCheckAlternateHostAvailable: false,
+ RPMAddToHistogram: false,
+ RPMRemoveMessageListener: false,
+ RPMGetHttpResponseHeader: false,
+ RPMTryPingSecureWWWLink: false,
+ RPMOpenSecureWWWLink: false,
+ RPMOpenPreferences: false,
+ RPMGetTRRSkipReason: false,
+ RPMGetTRRDomain: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/simpletest.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/simpletest.js
new file mode 100644
index 0000000000..2f5dd5c33e
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/simpletest.js
@@ -0,0 +1,35 @@
+/**
+ * @fileoverview Defines the environment for scripts that use the SimpleTest
+ * mochitest harness. Imports the globals from the relevant files.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+var path = require("path");
+var { getScriptGlobals } = require("./utils");
+
+// When updating this list, be sure to also update the 'support-files' config
+// in `tools/lint/eslint.yml`.
+const simpleTestFiles = [
+ "AccessibilityUtils.js",
+ "ExtensionTestUtils.js",
+ "EventUtils.js",
+ "MockObjects.js",
+ "SimpleTest.js",
+ "WindowSnapshot.js",
+ "paint_listener.js",
+];
+const simpleTestPath = "testing/mochitest/tests/SimpleTest";
+
+module.exports = getScriptGlobals(
+ "simpletest",
+ simpleTestFiles.map(file => path.join(simpleTestPath, file))
+);
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/sjs.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/sjs.js
new file mode 100644
index 0000000000..995c3173d2
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/sjs.js
@@ -0,0 +1,30 @@
+/**
+ * @fileoverview Defines the environment for sjs files.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ // All these variables are hard-coded to be available for sjs scopes only.
+ // https://searchfox.org/mozilla-central/rev/26a1b0fce12e6dd495a954c542bb1e7bd6e0d548/netwerk/test/httpserver/httpd.js#2879
+ atob: false,
+ btoa: false,
+ ChromeUtils: false,
+ dump: false,
+ getState: false,
+ setState: false,
+ getSharedState: false,
+ setSharedState: false,
+ getObjectState: false,
+ setObjectState: false,
+ registerPathHandler: false,
+ Services: false,
+ // importScripts is also available.
+ importScripts: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/special-powers-sandbox.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/special-powers-sandbox.js
new file mode 100644
index 0000000000..5a28c91883
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/special-powers-sandbox.js
@@ -0,0 +1,46 @@
+/**
+ * @fileoverview Defines the environment for SpecialPowers sandbox.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ // wantComponents defaults to true,
+ Components: false,
+ Ci: false,
+ Cr: false,
+ Cc: false,
+ Cu: false,
+ Services: false,
+
+ // testing/specialpowers/content/SpecialPowersSandbox.sys.mjs
+
+ // SANDBOX_GLOBALS
+ Blob: false,
+ ChromeUtils: false,
+ FileReader: false,
+ TextDecoder: false,
+ TextEncoder: false,
+ URL: false,
+
+ // EXTRA_IMPORTS
+ EventUtils: false,
+
+ // SpecialPowersSandbox constructor
+ assert: false,
+ Assert: false,
+ BrowsingContext: false,
+ InspectorUtils: false,
+ ok: false,
+ is: false,
+ isnot: false,
+ todo: false,
+ todo_is: false,
+ info: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/specific.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/specific.js
new file mode 100644
index 0000000000..23ebcb5bb1
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/specific.js
@@ -0,0 +1,31 @@
+/**
+ * @fileoverview Defines the environment for the Firefox browser. Allows global
+ * variables which are non-standard and specific to Firefox.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ globals: {
+ Cc: false,
+ ChromeUtils: false,
+ Ci: false,
+ Components: false,
+ Cr: false,
+ Cu: false,
+ Debugger: false,
+ InstallTrigger: false,
+ // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/InternalError
+ InternalError: true,
+ Services: false,
+ // https://developer.mozilla.org/docs/Web/API/Window/dump
+ dump: true,
+ openDialog: false,
+ // https://developer.mozilla.org/docs/Web/JavaScript/Reference/Global_Objects/uneval
+ uneval: false,
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/utils.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/utils.js
new file mode 100644
index 0000000000..aeda690ba5
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/utils.js
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Provides utilities for setting up environments.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var path = require("path");
+var helpers = require("../helpers");
+var globals = require("../globals");
+
+/**
+ * Obtains the globals for a list of files.
+ *
+ * @param {Array.<String>} files
+ * The array of files to get globals for. The paths are relative to the topsrcdir.
+ * @returns {Object}
+ * Returns an object with keys of the global names and values of if they are
+ * writable or not.
+ */
+function getGlobalsForScripts(environmentName, files, extraDefinitions) {
+ let fileGlobals = extraDefinitions;
+ const root = helpers.rootDir;
+ for (const file of files) {
+ const fileName = path.join(root, file);
+ try {
+ fileGlobals = fileGlobals.concat(globals.getGlobalsForFile(fileName));
+ } catch (e) {
+ console.error(`Could not load globals from file ${fileName}: ${e}`);
+ console.error(
+ `You may need to update the mappings for the ${environmentName} environment`
+ );
+ throw new Error(`Could not load globals from file ${fileName}: ${e}`);
+ }
+ }
+
+ var globalObjects = {};
+ for (let global of fileGlobals) {
+ globalObjects[global.name] = global.writable;
+ }
+ return globalObjects;
+}
+
+module.exports = {
+ getScriptGlobals(
+ environmentName,
+ files,
+ extraDefinitions = [],
+ extraEnv = {}
+ ) {
+ if (helpers.isMozillaCentralBased()) {
+ return {
+ globals: getGlobalsForScripts(environmentName, files, extraDefinitions),
+ ...extraEnv,
+ };
+ }
+ return helpers.getSavedEnvironmentItems(environmentName);
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/xpcshell.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/xpcshell.js
new file mode 100644
index 0000000000..408bc2e277
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/environments/xpcshell.js
@@ -0,0 +1,59 @@
+/**
+ * @fileoverview Defines the environment for xpcshell test files.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var { getScriptGlobals } = require("./utils");
+
+const extraGlobals = [
+ // Defined in XPCShellImpl.cpp
+ "print",
+ "readline",
+ "load",
+ "quit",
+ "dumpXPC",
+ "dump",
+ "gc",
+ "gczeal",
+ "options",
+ "sendCommand",
+ "atob",
+ "btoa",
+ "setInterruptCallback",
+ "simulateNoScriptActivity",
+ "registerXPCTestComponents",
+
+ // Assert.sys.mjs globals.
+ "setReporter",
+ "report",
+ "ok",
+ "equal",
+ "notEqual",
+ "deepEqual",
+ "notDeepEqual",
+ "strictEqual",
+ "notStrictEqual",
+ "throws",
+ "rejects",
+ "greater",
+ "greaterOrEqual",
+ "less",
+ "lessOrEqual",
+ // TestingFunctions.cpp globals
+ "allocationMarker",
+ "byteSize",
+ "saveStack",
+];
+
+module.exports = getScriptGlobals(
+ "xpcshell",
+ ["testing/xpcshell/head.js"],
+ extraGlobals.map(g => {
+ return { name: g, writable: false };
+ })
+);
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
new file mode 100644
index 0000000000..2c43c18799
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js
@@ -0,0 +1,434 @@
+/**
+ * @fileoverview functions for scanning an AST for globals including
+ * traversing referenced scripts.
+ * 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/.
+ */
+
+"use strict";
+
+const path = require("path");
+const fs = require("fs");
+const helpers = require("./helpers");
+const htmlparser = require("htmlparser2");
+
+/**
+ * Parses a list of "name:boolean_value" or/and "name" options divided by comma
+ * or whitespace.
+ *
+ * This function was copied from eslint.js
+ *
+ * @param {string} string The string to parse.
+ * @param {Comment} comment The comment node which has the string.
+ * @returns {Object} Result map object of names and boolean values
+ */
+function parseBooleanConfig(string, comment) {
+ let items = {};
+
+ // Collapse whitespace around : to make parsing easier
+ string = string.replace(/\s*:\s*/g, ":");
+ // Collapse whitespace around ,
+ string = string.replace(/\s*,\s*/g, ",");
+
+ string.split(/\s|,+/).forEach(function(name) {
+ if (!name) {
+ return;
+ }
+
+ let pos = name.indexOf(":");
+ let value;
+ if (pos !== -1) {
+ value = name.substring(pos + 1, name.length);
+ name = name.substring(0, pos);
+ }
+
+ items[name] = {
+ value: value === "true",
+ comment,
+ };
+ });
+
+ return items;
+}
+
+/**
+ * Global discovery can require parsing many files. This map of
+ * {String} => {Object} caches what globals were discovered for a file path.
+ */
+const globalCache = new Map();
+
+/**
+ * Global discovery can occasionally meet circular dependencies due to the way
+ * js files are included via html/xhtml files etc. This set is used to avoid
+ * getting into loops whilst the discovery is in progress.
+ */
+var globalDiscoveryInProgressForFiles = new Set();
+
+/**
+ * When looking for globals in HTML files, it can be common to have more than
+ * one script tag with inline javascript. These will normally be called together,
+ * so we store the globals for just the last HTML file processed.
+ */
+var lastHTMLGlobals = {};
+
+/**
+ * An object that returns found globals for given AST node types. Each prototype
+ * property should be named for a node type and accepts a node parameter and a
+ * parents parameter which is a list of the parent nodes of the current node.
+ * Each returns an array of globals found.
+ *
+ * @param {String} filePath
+ * The absolute path of the file being parsed.
+ */
+function GlobalsForNode(filePath, context) {
+ this.path = filePath;
+ this.context = context;
+
+ if (this.path) {
+ this.dirname = path.dirname(this.path);
+ } else {
+ this.dirname = null;
+ }
+}
+
+GlobalsForNode.prototype = {
+ Program(node) {
+ let globals = [];
+ for (let comment of node.comments) {
+ if (comment.type !== "Block") {
+ continue;
+ }
+ let value = comment.value.trim();
+ value = value.replace(/\n/g, "");
+
+ // We have to discover any globals that ESLint would have defined through
+ // comment directives.
+ let match = /^globals?\s+(.+)/.exec(value);
+ if (match) {
+ let values = parseBooleanConfig(match[1].trim(), node);
+ for (let name of Object.keys(values)) {
+ globals.push({
+ name,
+ writable: values[name].value,
+ });
+ }
+ // We matched globals, so we won't match import-globals-from.
+ continue;
+ }
+
+ match = /^import-globals-from\s+(.+)$/.exec(value);
+ if (!match) {
+ continue;
+ }
+
+ if (!this.dirname) {
+ // If this is testing context without path, ignore import.
+ return globals;
+ }
+
+ let filePath = match[1].trim();
+
+ if (filePath.endsWith(".mjs")) {
+ if (this.context) {
+ this.context.report(
+ comment,
+ "import-globals-from does not support module files - use a direct import instead"
+ );
+ } else {
+ // Fall back to throwing an error, as we do not have a context in all situations,
+ // e.g. when loading the environment.
+ throw new Error(
+ "import-globals-from does not support module files - use a direct import instead"
+ );
+ }
+ continue;
+ }
+
+ if (!path.isAbsolute(filePath)) {
+ filePath = path.resolve(this.dirname, filePath);
+ } else {
+ filePath = path.join(helpers.rootDir, filePath);
+ }
+ globals = globals.concat(module.exports.getGlobalsForFile(filePath));
+ }
+
+ return globals;
+ },
+
+ ExpressionStatement(node, parents, globalScope) {
+ let isGlobal = helpers.getIsGlobalThis(parents);
+ let globals = [];
+
+ // Note: We check the expression types here and only call the necessary
+ // functions to aid performance.
+ if (node.expression.type === "AssignmentExpression") {
+ globals = helpers.convertThisAssignmentExpressionToGlobals(
+ node,
+ isGlobal
+ );
+ } else if (node.expression.type === "CallExpression") {
+ globals = helpers.convertCallExpressionToGlobals(node, isGlobal);
+ }
+
+ // Here we assume that if importScripts is set in the global scope, then
+ // this is a worker. It would be nice if eslint gave us a way of getting
+ // the environment directly.
+ //
+ // If this is testing context without path, ignore import.
+ if (globalScope && globalScope.set.get("importScripts") && this.dirname) {
+ let workerDetails = helpers.convertWorkerExpressionToGlobals(
+ node,
+ isGlobal,
+ this.dirname
+ );
+ globals = globals.concat(workerDetails);
+ }
+
+ return globals;
+ },
+};
+
+module.exports = {
+ /**
+ * Returns all globals for a given file. Recursively searches through
+ * import-globals-from directives and also includes globals defined by
+ * standard eslint directives.
+ *
+ * @param {String} filePath
+ * The absolute path of the file to be parsed.
+ * @param {Object} astOptions
+ * Extra options to pass to the parser.
+ * @return {Array}
+ * An array of objects that contain details about the globals:
+ * - {String} name
+ * The name of the global.
+ * - {Boolean} writable
+ * If the global is writeable or not.
+ */
+ getGlobalsForFile(filePath, astOptions = {}) {
+ if (globalCache.has(filePath)) {
+ return globalCache.get(filePath);
+ }
+
+ if (globalDiscoveryInProgressForFiles.has(filePath)) {
+ // We're already processing this file, so return an empty set for now -
+ // the initial processing will pick up on the globals for this file.
+ return [];
+ }
+ globalDiscoveryInProgressForFiles.add(filePath);
+
+ let content = fs.readFileSync(filePath, "utf8");
+
+ // Parse the content into an AST
+ let { ast, scopeManager, visitorKeys } = helpers.parseCode(
+ content,
+ astOptions
+ );
+
+ // Discover global declarations
+ let globalScope = scopeManager.acquire(ast);
+
+ let globals = Object.keys(globalScope.variables).map(v => ({
+ name: globalScope.variables[v].name,
+ writable: true,
+ }));
+
+ // Walk over the AST to find any of our custom globals
+ let handler = new GlobalsForNode(filePath);
+
+ helpers.walkAST(ast, visitorKeys, (type, node, parents) => {
+ if (type in handler) {
+ let newGlobals = handler[type](node, parents, globalScope);
+ globals.push.apply(globals, newGlobals);
+ }
+ });
+
+ globalCache.set(filePath, globals);
+
+ globalDiscoveryInProgressForFiles.delete(filePath);
+ return globals;
+ },
+
+ /**
+ * Returns all globals for a code.
+ * This is only for testing.
+ *
+ * @param {String} code
+ * The JS code
+ * @param {Object} astOptions
+ * Extra options to pass to the parser.
+ * @return {Array}
+ * An array of objects that contain details about the globals:
+ * - {String} name
+ * The name of the global.
+ * - {Boolean} writable
+ * If the global is writeable or not.
+ */
+ getGlobalsForCode(code, astOptions = {}) {
+ // Parse the content into an AST
+ let { ast, scopeManager, visitorKeys } = helpers.parseCode(
+ code,
+ astOptions,
+ { useBabel: false }
+ );
+
+ // Discover global declarations
+ let globalScope = scopeManager.acquire(ast);
+
+ let globals = Object.keys(globalScope.variables).map(v => ({
+ name: globalScope.variables[v].name,
+ writable: true,
+ }));
+
+ // Walk over the AST to find any of our custom globals
+ let handler = new GlobalsForNode(null);
+
+ helpers.walkAST(ast, visitorKeys, (type, node, parents) => {
+ if (type in handler) {
+ let newGlobals = handler[type](node, parents, globalScope);
+ globals.push.apply(globals, newGlobals);
+ }
+ });
+
+ return globals;
+ },
+
+ /**
+ * Returns all the globals for an html file that are defined by imported
+ * scripts (i.e. <script src="foo.js">).
+ *
+ * This function will cache results for one html file only - we expect
+ * this to be called sequentially for each chunk of a HTML file, rather
+ * than chucks of different files in random order.
+ *
+ * @param {String} filePath
+ * The absolute path of the file to be parsed.
+ * @return {Array}
+ * An array of objects that contain details about the globals:
+ * - {String} name
+ * The name of the global.
+ * - {Boolean} writable
+ * If the global is writeable or not.
+ */
+ getImportedGlobalsForHTMLFile(filePath) {
+ if (lastHTMLGlobals.filename === filePath) {
+ return lastHTMLGlobals.globals;
+ }
+
+ let dir = path.dirname(filePath);
+ let globals = [];
+
+ let content = fs.readFileSync(filePath, "utf8");
+ let scriptSrcs = [];
+
+ // We use htmlparser as this ensures we find the script tags correctly.
+ let parser = new htmlparser.Parser(
+ {
+ onopentag(name, attribs) {
+ if (name === "script" && "src" in attribs) {
+ scriptSrcs.push({
+ src: attribs.src,
+ type:
+ "type" in attribs && attribs.type == "module"
+ ? "module"
+ : "script",
+ });
+ }
+ },
+ },
+ {
+ xmlMode: filePath.endsWith("xhtml"),
+ }
+ );
+
+ parser.parseComplete(content);
+
+ for (let script of scriptSrcs) {
+ // Ensure that the script src isn't just "".
+ if (!script.src) {
+ continue;
+ }
+ let scriptName;
+ if (script.src.includes("http:")) {
+ // We don't handle this currently as the paths are complex to match.
+ } else if (script.src.includes("chrome")) {
+ // This is one way of referencing test files.
+ script.src = script.src.replace("chrome://mochikit/content/", "/");
+ scriptName = path.join(
+ helpers.rootDir,
+ "testing",
+ "mochitest",
+ script.src
+ );
+ } else if (script.src.includes("SimpleTest")) {
+ // This is another way of referencing test files...
+ scriptName = path.join(
+ helpers.rootDir,
+ "testing",
+ "mochitest",
+ script.src
+ );
+ } else if (script.src.startsWith("/tests/")) {
+ scriptName = path.join(helpers.rootDir, script.src.substring(7));
+ } else {
+ // Fallback to hoping this is a relative path.
+ scriptName = path.join(dir, script.src);
+ }
+ if (scriptName && fs.existsSync(scriptName)) {
+ globals.push(
+ ...module.exports.getGlobalsForFile(scriptName, {
+ ecmaVersion: helpers.getECMAVersion(),
+ sourceType: script.type,
+ })
+ );
+ }
+ }
+
+ lastHTMLGlobals.filePath = filePath;
+ return (lastHTMLGlobals.globals = globals);
+ },
+
+ /**
+ * Intended to be used as-is for an ESLint rule that parses for globals in
+ * the current file and recurses through import-globals-from directives.
+ *
+ * @param {Object} context
+ * The ESLint parsing context.
+ */
+ getESLintGlobalParser(context) {
+ let globalScope;
+
+ let parser = {
+ Program(node) {
+ globalScope = context.getScope();
+ },
+ };
+ let filename = context.getFilename();
+
+ let extraHTMLGlobals = [];
+ if (filename.endsWith(".html") || filename.endsWith(".xhtml")) {
+ extraHTMLGlobals = module.exports.getImportedGlobalsForHTMLFile(filename);
+ }
+
+ // Install thin wrappers around GlobalsForNode
+ let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context));
+
+ for (let type of Object.keys(GlobalsForNode.prototype)) {
+ parser[type] = function(node) {
+ if (type === "Program") {
+ globalScope = context.getScope();
+ helpers.addGlobals(extraHTMLGlobals, globalScope);
+ }
+ let globals = handler[type](node, context.getAncestors(), globalScope);
+ helpers.addGlobals(
+ globals,
+ globalScope,
+ node.type !== "Program" && node
+ );
+ };
+ }
+
+ return parser;
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
new file mode 100644
index 0000000000..823242cf92
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js
@@ -0,0 +1,1015 @@
+/**
+ * @fileoverview A collection of helper functions.
+ * 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/.
+ */
+"use strict";
+
+const parser = require("@babel/eslint-parser");
+const { analyze } = require("eslint-scope");
+const { KEYS: defaultVisitorKeys } = require("eslint-visitor-keys");
+const estraverse = require("estraverse");
+const path = require("path");
+const fs = require("fs");
+const ini = require("multi-ini");
+const recommendedConfig = require("./configs/recommended");
+
+var gRootDir = null;
+var directoryManifests = new Map();
+
+const callExpressionDefinitions = [
+ /^loader\.lazyGetter\((?:globalThis|this), "(\w+)"/,
+ /^loader\.lazyServiceGetter\((?:globalThis|this), "(\w+)"/,
+ /^loader\.lazyRequireGetter\((?:globalThis|this), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/,
+ /^ChromeUtils\.defineModuleGetter\((?:globalThis|this), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyPreferenceGetter\((?:globalThis|this), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyProxy\((?:globalThis|this), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyScriptGetter\((?:globalThis|this), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyServiceGetter\((?:globalThis|this), "(\w+)"/,
+ /^XPCOMUtils\.defineConstant\((?:globalThis|this), "(\w+)"/,
+ /^DevToolsUtils\.defineLazyModuleGetter\((?:globalThis|this), "(\w+)"/,
+ /^DevToolsUtils\.defineLazyGetter\((?:globalThis|this), "(\w+)"/,
+ /^Object\.defineProperty\((?:globalThis|this), "(\w+)"/,
+ /^Reflect\.defineProperty\((?:globalThis|this), "(\w+)"/,
+ /^this\.__defineGetter__\("(\w+)"/,
+];
+
+const callExpressionMultiDefinitions = [
+ "XPCOMUtils.defineLazyGlobalGetters(this,",
+ "XPCOMUtils.defineLazyGlobalGetters(globalThis,",
+ "XPCOMUtils.defineLazyModuleGetters(this,",
+ "XPCOMUtils.defineLazyModuleGetters(globalThis,",
+ "XPCOMUtils.defineLazyServiceGetters(this,",
+ "XPCOMUtils.defineLazyServiceGetters(globalThis,",
+ "ChromeUtils.defineESModuleGetters(this,",
+ "ChromeUtils.defineESModuleGetters(globalThis,",
+ "loader.lazyRequireGetter(this,",
+ "loader.lazyRequireGetter(globalThis,",
+];
+
+const workerImportFilenameMatch = /(.*\/)*((.*?)\.jsm?)/;
+
+let xpidlData;
+
+module.exports = {
+ get iniParser() {
+ if (!this._iniParser) {
+ this._iniParser = new ini.Parser();
+ }
+ return this._iniParser;
+ },
+
+ get servicesData() {
+ return require("./services.json");
+ },
+
+ /**
+ * Obtains xpidl data from the object directory specified in the
+ * environment.
+ *
+ * @returns {Map<string, object>}
+ * A map of interface names to the interface details.
+ */
+ get xpidlData() {
+ let xpidlDir;
+
+ if (process.env.TASK_ID && !process.env.MOZ_XPT_ARTIFACTS_DIR) {
+ throw new Error(
+ "MOZ_XPT_ARTIFACTS_DIR must be set for this rule in automation"
+ );
+ }
+ xpidlDir = process.env.MOZ_XPT_ARTIFACTS_DIR;
+
+ if (!xpidlDir && process.env.MOZ_OBJDIR) {
+ xpidlDir = `${process.env.MOZ_OBJDIR}/dist/xpt_artifacts/`;
+ if (!fs.existsSync(xpidlDir)) {
+ xpidlDir = `${process.env.MOZ_OBJDIR}/config/makefiles/xpidl/`;
+ }
+ }
+ if (!xpidlDir) {
+ throw new Error(
+ "MOZ_OBJDIR must be defined in the environment for this rule, i.e. MOZ_OBJDIR=objdir-ff ./mach ..."
+ );
+ }
+ if (xpidlData) {
+ return xpidlData;
+ }
+ let files = fs.readdirSync(`${xpidlDir}`);
+ // `Makefile` is an expected file in the directory.
+ if (files.length <= 1) {
+ throw new Error("Missing xpidl data files, maybe you need to build?");
+ }
+ xpidlData = new Map();
+ for (let file of files) {
+ if (!file.endsWith(".xpt")) {
+ continue;
+ }
+ let data = JSON.parse(fs.readFileSync(path.join(`${xpidlDir}`, file)));
+ for (let details of data) {
+ xpidlData.set(details.name, details);
+ }
+ }
+ return xpidlData;
+ },
+
+ /**
+ * Gets the abstract syntax tree (AST) of the JavaScript source code contained
+ * in sourceText. This matches the results for an eslint parser, see
+ * https://eslint.org/docs/developer-guide/working-with-custom-parsers.
+ *
+ * @param {String} sourceText
+ * Text containing valid JavaScript.
+ * @param {Object} astOptions
+ * Extra configuration to pass to the espree parser, these will override
+ * the configuration from getPermissiveConfig().
+ * @param {Object} configOptions
+ * Extra options for getPermissiveConfig().
+ *
+ * @return {Object}
+ * Returns an object containing `ast`, `scopeManager` and
+ * `visitorKeys`
+ */
+ parseCode(sourceText, astOptions = {}, configOptions = {}) {
+ // Use a permissive config file to allow parsing of anything that Espree
+ // can parse.
+ let config = { ...this.getPermissiveConfig(configOptions), ...astOptions };
+
+ let parseResult =
+ "parseForESLint" in parser
+ ? parser.parseForESLint(sourceText, config)
+ : { ast: parser.parse(sourceText, config) };
+
+ let visitorKeys = parseResult.visitorKeys || defaultVisitorKeys;
+ visitorKeys.ExperimentalRestProperty = visitorKeys.RestElement;
+ visitorKeys.ExperimentalSpreadProperty = visitorKeys.SpreadElement;
+
+ return {
+ ast: parseResult.ast,
+ scopeManager: parseResult.scopeManager || analyze(parseResult.ast),
+ visitorKeys,
+ };
+ },
+
+ /**
+ * A simplistic conversion of some AST nodes to a standard string form.
+ *
+ * @param {Object} node
+ * The AST node to convert.
+ *
+ * @return {String}
+ * The JS source for the node.
+ */
+ getASTSource(node, context) {
+ switch (node.type) {
+ case "MemberExpression":
+ if (node.computed) {
+ let filename = context && context.getFilename();
+ throw new Error(
+ `getASTSource unsupported computed MemberExpression in ${filename}`
+ );
+ }
+ return (
+ this.getASTSource(node.object) +
+ "." +
+ this.getASTSource(node.property)
+ );
+ case "ThisExpression":
+ return "this";
+ case "Identifier":
+ return node.name;
+ case "Literal":
+ return JSON.stringify(node.value);
+ case "CallExpression":
+ var args = node.arguments.map(a => this.getASTSource(a)).join(", ");
+ return this.getASTSource(node.callee) + "(" + args + ")";
+ case "ObjectExpression":
+ return "{}";
+ case "ExpressionStatement":
+ return this.getASTSource(node.expression) + ";";
+ case "FunctionExpression":
+ return "function() {}";
+ case "ArrayExpression":
+ return "[" + node.elements.map(this.getASTSource, this).join(",") + "]";
+ case "ArrowFunctionExpression":
+ return "() => {}";
+ case "AssignmentExpression":
+ return (
+ this.getASTSource(node.left) + " = " + this.getASTSource(node.right)
+ );
+ case "BinaryExpression":
+ return (
+ this.getASTSource(node.left) +
+ " " +
+ node.operator +
+ " " +
+ this.getASTSource(node.right)
+ );
+ case "UnaryExpression":
+ return node.operator + " " + this.getASTSource(node.argument);
+ default:
+ throw new Error("getASTSource unsupported node type: " + node.type);
+ }
+ },
+
+ /**
+ * This walks an AST in a manner similar to ESLint passing node events to the
+ * listener. The listener is expected to be a simple function
+ * which accepts node type, node and parents arguments.
+ *
+ * @param {Object} ast
+ * The AST to walk.
+ * @param {Array} visitorKeys
+ * The visitor keys to use for the AST.
+ * @param {Function} listener
+ * A callback function to call for the nodes. Passed three arguments,
+ * event type, node and an array of parent nodes for the current node.
+ */
+ walkAST(ast, visitorKeys, listener) {
+ let parents = [];
+
+ estraverse.traverse(ast, {
+ enter(node, parent) {
+ listener(node.type, node, parents);
+
+ parents.push(node);
+ },
+
+ leave(node, parent) {
+ if (!parents.length) {
+ throw new Error("Left more nodes than entered.");
+ }
+ parents.pop();
+ },
+
+ keys: visitorKeys,
+ });
+ if (parents.length) {
+ throw new Error("Entered more nodes than left.");
+ }
+ },
+
+ /**
+ * Attempts to convert an ExpressionStatement to likely global variable
+ * definitions.
+ *
+ * @param {Object} node
+ * The AST node to convert.
+ * @param {boolean} isGlobal
+ * True if the current node is in the global scope.
+ *
+ * @return {Array}
+ * An array of objects that contain details about the globals:
+ * - {String} name
+ * The name of the global.
+ * - {Boolean} writable
+ * If the global is writeable or not.
+ */
+ convertWorkerExpressionToGlobals(node, isGlobal, dirname) {
+ var getGlobalsForFile = require("./globals").getGlobalsForFile;
+
+ let results = [];
+ let expr = node.expression;
+
+ if (
+ node.expression.type === "CallExpression" &&
+ expr.callee &&
+ expr.callee.type === "Identifier" &&
+ expr.callee.name === "importScripts"
+ ) {
+ for (var arg of expr.arguments) {
+ var match = arg.value && arg.value.match(workerImportFilenameMatch);
+ if (match) {
+ if (!match[1]) {
+ let filePath = path.resolve(dirname, match[2]);
+ if (fs.existsSync(filePath)) {
+ let additionalGlobals = getGlobalsForFile(filePath);
+ results = results.concat(additionalGlobals);
+ }
+ }
+ // Import with relative/absolute path should explicitly use
+ // `import-globals-from` comment.
+ }
+ }
+ }
+
+ return results;
+ },
+
+ /**
+ * Attempts to convert an AssignmentExpression into a global variable
+ * definition if it applies to `this` in the global scope.
+ *
+ * @param {Object} node
+ * The AST node to convert.
+ * @param {boolean} isGlobal
+ * True if the current node is in the global scope.
+ *
+ * @return {Array}
+ * An array of objects that contain details about the globals:
+ * - {String} name
+ * The name of the global.
+ * - {Boolean} writable
+ * If the global is writeable or not.
+ */
+ convertThisAssignmentExpressionToGlobals(node, isGlobal) {
+ if (
+ isGlobal &&
+ node.expression.left &&
+ node.expression.left.object &&
+ node.expression.left.object.type === "ThisExpression" &&
+ node.expression.left.property &&
+ node.expression.left.property.type === "Identifier"
+ ) {
+ return [{ name: node.expression.left.property.name, writable: true }];
+ }
+ return [];
+ },
+
+ /**
+ * Attempts to convert an CallExpressions that look like module imports
+ * into global variable definitions.
+ *
+ * @param {Object} node
+ * The AST node to convert.
+ * @param {boolean} isGlobal
+ * True if the current node is in the global scope.
+ *
+ * @return {Array}
+ * An array of objects that contain details about the globals:
+ * - {String} name
+ * The name of the global.
+ * - {Boolean} writable
+ * If the global is writeable or not.
+ */
+ convertCallExpressionToGlobals(node, isGlobal) {
+ let express = node.expression;
+ if (
+ express.type === "CallExpression" &&
+ express.callee.type === "MemberExpression" &&
+ express.callee.object &&
+ express.callee.object.type === "Identifier" &&
+ express.arguments.length === 1 &&
+ express.arguments[0].type === "ArrayExpression" &&
+ express.callee.property.type === "Identifier" &&
+ express.callee.property.name === "importGlobalProperties"
+ ) {
+ return express.arguments[0].elements.map(literal => {
+ return {
+ explicit: true,
+ name: literal.value,
+ writable: false,
+ };
+ });
+ }
+
+ let source;
+ try {
+ source = this.getASTSource(node);
+ } catch (e) {
+ return [];
+ }
+
+ // The definition matches below must be in the global scope for us to define
+ // a global, so bail out early if we're not a global.
+ if (!isGlobal) {
+ return [];
+ }
+
+ for (let reg of callExpressionDefinitions) {
+ let match = source.match(reg);
+ if (match) {
+ return [{ name: match[1], writable: true, explicit: true }];
+ }
+ }
+
+ if (
+ callExpressionMultiDefinitions.some(expr => source.startsWith(expr)) &&
+ node.expression.arguments[1]
+ ) {
+ let arg = node.expression.arguments[1];
+ if (arg.type === "ObjectExpression") {
+ return arg.properties
+ .map(p => ({
+ name: p.type === "Property" && p.key.name,
+ writable: true,
+ explicit: true,
+ }))
+ .filter(g => g.name);
+ }
+ if (arg.type === "ArrayExpression") {
+ return arg.elements
+ .map(p => ({
+ name: p.type === "Literal" && p.value,
+ writable: true,
+ explicit: true,
+ }))
+ .filter(g => typeof g.name == "string");
+ }
+ }
+
+ if (
+ node.expression.callee.type == "MemberExpression" &&
+ node.expression.callee.property.type == "Identifier" &&
+ node.expression.callee.property.name == "defineLazyScriptGetter"
+ ) {
+ // The case where we have a single symbol as a string has already been
+ // handled by the regexp, so we have an array of symbols here.
+ return node.expression.arguments[1].elements.map(n => ({
+ name: n.value,
+ writable: true,
+ explicit: true,
+ }));
+ }
+
+ return [];
+ },
+
+ /**
+ * Add a variable to the current scope.
+ * HACK: This relies on eslint internals so it could break at any time.
+ *
+ * @param {String} name
+ * The variable name to add to the scope.
+ * @param {ASTScope} scope
+ * The scope to add to.
+ * @param {boolean} writable
+ * Whether the global can be overwritten.
+ * @param {Object} [node]
+ * The AST node that defined the globals.
+ */
+ addVarToScope(name, scope, writable, node) {
+ scope.__defineGeneric(name, scope.set, scope.variables, null, null);
+
+ let variable = scope.set.get(name);
+ variable.eslintExplicitGlobal = false;
+ variable.writeable = writable;
+ if (node) {
+ variable.defs.push({
+ type: "Variable",
+ node,
+ name: { name, parent: node.parent },
+ });
+ variable.identifiers.push(node);
+ }
+
+ // Walk to the global scope which holds all undeclared variables.
+ while (scope.type != "global") {
+ scope = scope.upper;
+ }
+
+ // "through" contains all references with no found definition.
+ scope.through = scope.through.filter(function(reference) {
+ if (reference.identifier.name != name) {
+ return true;
+ }
+
+ // Links the variable and the reference.
+ // And this reference is removed from `Scope#through`.
+ reference.resolved = variable;
+ variable.references.push(reference);
+ return false;
+ });
+ },
+
+ /**
+ * Adds a set of globals to a scope.
+ *
+ * @param {Array} globalVars
+ * An array of global variable names.
+ * @param {ASTScope} scope
+ * The scope.
+ * @param {Object} [node]
+ * The AST node that defined the globals.
+ */
+ addGlobals(globalVars, scope, node) {
+ globalVars.forEach(v =>
+ this.addVarToScope(v.name, scope, v.writable, v.explicit && node)
+ );
+ },
+
+ /**
+ * To allow espree to parse almost any JavaScript we need as many features as
+ * possible turned on. This method returns that config.
+ *
+ * @param {Object} options
+ * {
+ * useBabel: {boolean} whether to set babelOptions.
+ * }
+ * @return {Object}
+ * Espree compatible permissive config.
+ */
+ getPermissiveConfig({ useBabel = true } = {}) {
+ const config = {
+ range: true,
+ requireConfigFile: false,
+ babelOptions: {
+ // configFile: path.join(gRootDir, ".babel-eslint.rc.js"),
+ // parserOpts: {
+ // plugins: [
+ // "@babel/plugin-proposal-class-static-block",
+ // "@babel/plugin-syntax-class-properties",
+ // "@babel/plugin-syntax-jsx",
+ // ],
+ // },
+ },
+ loc: true,
+ comment: true,
+ attachComment: true,
+ ecmaVersion: this.getECMAVersion(),
+ sourceType: "script",
+ };
+
+ if (useBabel && this.isMozillaCentralBased()) {
+ config.babelOptions.configFile = path.join(
+ gRootDir,
+ ".babel-eslint.rc.js"
+ );
+ }
+ return config;
+ },
+
+ /**
+ * Returns the ECMA version of the recommended config.
+ *
+ * @return {Number} The ECMA version of the recommended config.
+ */
+ getECMAVersion() {
+ return recommendedConfig.parserOptions.ecmaVersion;
+ },
+
+ /**
+ * Check whether it's inside top-level script.
+ *
+ * @param {Array} ancestors
+ * The parents of the current node.
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsTopLevelScript(ancestors) {
+ for (let parent of ancestors) {
+ switch (parent.type) {
+ case "ArrowFunctionExpression":
+ case "FunctionDeclaration":
+ case "FunctionExpression":
+ case "PropertyDefinition":
+ case "StaticBlock":
+ return false;
+ }
+ }
+ return true;
+ },
+
+ isTopLevel(ancestors) {
+ for (let parent of ancestors) {
+ switch (parent.type) {
+ case "ArrowFunctionExpression":
+ case "FunctionDeclaration":
+ case "FunctionExpression":
+ case "PropertyDefinition":
+ case "StaticBlock":
+ case "BlockStatement":
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Check whether `this` expression points the global this.
+ *
+ * @param {Array} ancestors
+ * The parents of the current node.
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsGlobalThis(ancestors) {
+ for (let parent of ancestors) {
+ switch (parent.type) {
+ case "FunctionDeclaration":
+ case "FunctionExpression":
+ case "PropertyDefinition":
+ case "StaticBlock":
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Check whether the node is evaluated at top-level script unconditionally.
+ *
+ * @param {Array} ancestors
+ * The parents of the current node.
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsTopLevelAndUnconditionallyExecuted(ancestors) {
+ for (let parent of ancestors) {
+ switch (parent.type) {
+ // Control flow
+ case "IfStatement":
+ case "SwitchStatement":
+ case "TryStatement":
+ case "WhileStatement":
+ case "DoWhileStatement":
+ case "ForStatement":
+ case "ForInStatement":
+ case "ForOfStatement":
+ return false;
+
+ // Function
+ case "FunctionDeclaration":
+ case "FunctionExpression":
+ case "ArrowFunctionExpression":
+ case "ClassBody":
+ return false;
+
+ // Branch
+ case "LogicalExpression":
+ case "ConditionalExpression":
+ case "ChainExpression":
+ return false;
+
+ case "AssignmentExpression":
+ switch (parent.operator) {
+ // Branch
+ case "||=":
+ case "&&=":
+ case "??=":
+ return false;
+ }
+ break;
+
+ // Implicit branch (default value)
+ case "ObjectPattern":
+ case "ArrayPattern":
+ return false;
+ }
+ }
+ return true;
+ },
+
+ /**
+ * Check whether we might be in a test head file.
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(context)
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsHeadFile(scope) {
+ var pathAndFilename = this.cleanUpPath(scope.getFilename());
+
+ return /.*[\\/]head(_.+)?\.js$/.test(pathAndFilename);
+ },
+
+ /**
+ * Gets the head files for a potential test file
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(context)
+ *
+ * @return {String[]}
+ * Paths to head files to load for the test
+ */
+ getTestHeadFiles(scope) {
+ if (!this.getIsTest(scope)) {
+ return [];
+ }
+
+ let filepath = this.cleanUpPath(scope.getFilename());
+ let dir = path.dirname(filepath);
+
+ let names = fs
+ .readdirSync(dir)
+ .filter(
+ name =>
+ (name.startsWith("head") || name.startsWith("xpcshell-head")) &&
+ name.endsWith(".js")
+ )
+ .map(name => path.join(dir, name));
+ return names;
+ },
+
+ /**
+ * Gets all the test manifest data for a directory
+ *
+ * @param {String} dir
+ * The directory
+ *
+ * @return {Array}
+ * An array of objects with file and manifest properties
+ */
+ getManifestsForDirectory(dir) {
+ if (directoryManifests.has(dir)) {
+ return directoryManifests.get(dir);
+ }
+
+ let manifests = [];
+ let names = [];
+ try {
+ names = fs.readdirSync(dir);
+ } catch (err) {
+ // Ignore directory not found, it might be faked by a test
+ if (err.code !== "ENOENT") {
+ throw err;
+ }
+ }
+
+ for (let name of names) {
+ if (!name.endsWith(".ini")) {
+ continue;
+ }
+
+ try {
+ let manifest = this.iniParser.parse(
+ fs.readFileSync(path.join(dir, name), "utf8").split("\n")
+ );
+ manifests.push({
+ file: path.join(dir, name),
+ manifest,
+ });
+ } catch (e) {}
+ }
+
+ directoryManifests.set(dir, manifests);
+ return manifests;
+ },
+
+ /**
+ * Gets the manifest file a test is listed in
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(context)
+ *
+ * @return {String}
+ * The path to the test manifest file
+ */
+ getTestManifest(scope) {
+ let filepath = this.cleanUpPath(scope.getFilename());
+
+ let dir = path.dirname(filepath);
+ let filename = path.basename(filepath);
+
+ for (let manifest of this.getManifestsForDirectory(dir)) {
+ if (filename in manifest.manifest) {
+ return manifest.file;
+ }
+ }
+
+ return null;
+ },
+
+ /**
+ * Check whether we are in a test of some kind.
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsTest(context)
+ *
+ * @return {Boolean}
+ * True or false
+ */
+ getIsTest(scope) {
+ // Regardless of the manifest name being in a manifest means we're a test.
+ let manifest = this.getTestManifest(scope);
+ if (manifest) {
+ return true;
+ }
+
+ return !!this.getTestType(scope);
+ },
+
+ /*
+ * Check if this is an .sjs file.
+ */
+ getIsSjs(scope) {
+ let filepath = this.cleanUpPath(scope.getFilename());
+
+ return path.extname(filepath) == ".sjs";
+ },
+
+ /**
+ * Gets the type of test or null if this isn't a test.
+ *
+ * @param {RuleContext} scope
+ * You should pass this from within a rule
+ * e.g. helpers.getIsHeadFile(context)
+ *
+ * @return {String or null}
+ * Test type: xpcshell, browser, chrome, mochitest
+ */
+ getTestType(scope) {
+ let testTypes = ["browser", "xpcshell", "chrome", "mochitest", "a11y"];
+ let manifest = this.getTestManifest(scope);
+ if (manifest) {
+ let name = path.basename(manifest);
+ for (let testType of testTypes) {
+ if (name.startsWith(testType)) {
+ return testType;
+ }
+ }
+ }
+
+ let filepath = this.cleanUpPath(scope.getFilename());
+ let filename = path.basename(filepath);
+
+ if (filename.startsWith("browser_")) {
+ return "browser";
+ }
+
+ if (filename.startsWith("test_")) {
+ let parent = path.basename(path.dirname(filepath));
+ for (let testType of testTypes) {
+ if (parent.startsWith(testType)) {
+ return testType;
+ }
+ }
+
+ // It likely is a test, we're just not sure what kind.
+ return "unknown";
+ }
+
+ // Likely not a test
+ return null;
+ },
+
+ getIsWorker(filePath) {
+ let filename = path.basename(this.cleanUpPath(filePath)).toLowerCase();
+
+ return filename.includes("worker");
+ },
+
+ /**
+ * Gets the root directory of the repository by walking up directories from
+ * this file until a .eslintignore file is found. If this fails, the same
+ * procedure will be attempted from the current working dir.
+ * @return {String} The absolute path of the repository directory
+ */
+ get rootDir() {
+ if (!gRootDir) {
+ function searchUpForIgnore(dirName, filename) {
+ let parsed = path.parse(dirName);
+ while (parsed.root !== dirName) {
+ if (fs.existsSync(path.join(dirName, filename))) {
+ return dirName;
+ }
+ // Move up a level
+ dirName = parsed.dir;
+ parsed = path.parse(dirName);
+ }
+ return null;
+ }
+
+ let possibleRoot = searchUpForIgnore(
+ path.dirname(module.filename),
+ ".eslintignore"
+ );
+ if (!possibleRoot) {
+ possibleRoot = searchUpForIgnore(path.resolve(), ".eslintignore");
+ }
+ if (!possibleRoot) {
+ possibleRoot = searchUpForIgnore(path.resolve(), "package.json");
+ }
+ if (!possibleRoot) {
+ // We've couldn't find a root from the module or CWD, so lets just go
+ // for the CWD. We really don't want to throw if possible, as that
+ // tends to give confusing results when used with ESLint.
+ possibleRoot = process.cwd();
+ }
+
+ gRootDir = possibleRoot;
+ }
+
+ return gRootDir;
+ },
+
+ /**
+ * ESLint may be executed from various places: from mach, at the root of the
+ * repository, or from a directory in the repository when, for instance,
+ * executed by a text editor's plugin.
+ * The value returned by context.getFileName() varies because of this.
+ * This helper function makes sure to return an absolute file path for the
+ * current context, by looking at process.cwd().
+ * @param {Context} context
+ * @return {String} The absolute path
+ */
+ getAbsoluteFilePath(context) {
+ var fileName = this.cleanUpPath(context.getFilename());
+ var cwd = process.cwd();
+
+ if (path.isAbsolute(fileName)) {
+ // Case 2: executed from the repo's root with mach:
+ // fileName: /path/to/mozilla/repo/a/b/c/d.js
+ // cwd: /path/to/mozilla/repo
+ return fileName;
+ } else if (path.basename(fileName) == fileName) {
+ // Case 1b: executed from a nested directory, fileName is the base name
+ // without any path info (happens in Atom with linter-eslint)
+ return path.join(cwd, fileName);
+ }
+ // Case 1: executed form in a nested directory, e.g. from a text editor:
+ // fileName: a/b/c/d.js
+ // cwd: /path/to/mozilla/repo/a/b/c
+ var dirName = path.dirname(fileName);
+ return cwd.slice(0, cwd.length - dirName.length) + fileName;
+ },
+
+ /**
+ * When ESLint is run from SublimeText, paths retrieved from
+ * context.getFileName contain leading and trailing double-quote characters.
+ * These characters need to be removed.
+ */
+ cleanUpPath(pathName) {
+ return pathName.replace(/^"/, "").replace(/"$/, "");
+ },
+
+ get globalScriptPaths() {
+ return [
+ path.join(this.rootDir, "browser", "base", "content", "browser.xhtml"),
+ path.join(
+ this.rootDir,
+ "browser",
+ "base",
+ "content",
+ "global-scripts.inc"
+ ),
+ ];
+ },
+
+ isMozillaCentralBased() {
+ return fs.existsSync(this.globalScriptPaths[0]);
+ },
+
+ getSavedEnvironmentItems(environment) {
+ return require("./environments/saved-globals.json").environments[
+ environment
+ ];
+ },
+
+ getSavedRuleData(rule) {
+ return require("./rules/saved-rules-data.json").rulesData[rule];
+ },
+
+ getBuildEnvironment() {
+ var { execFileSync } = require("child_process");
+ var output = execFileSync(
+ path.join(this.rootDir, "mach"),
+ ["environment", "--format=json"],
+ { silent: true }
+ );
+ return JSON.parse(output);
+ },
+
+ /**
+ * Extract the path of require (and require-like) helpers used in DevTools.
+ */
+ getDevToolsRequirePath(node) {
+ if (
+ node.callee.type == "Identifier" &&
+ node.callee.name == "require" &&
+ node.arguments.length == 1 &&
+ node.arguments[0].type == "Literal"
+ ) {
+ return node.arguments[0].value;
+ } else if (
+ node.callee.type == "MemberExpression" &&
+ node.callee.property.type == "Identifier" &&
+ node.callee.property.name == "lazyRequireGetter" &&
+ node.arguments.length >= 3 &&
+ node.arguments[2].type == "Literal"
+ ) {
+ return node.arguments[2].value;
+ }
+ return null;
+ },
+
+ /**
+ * Returns property name from MemberExpression. Also accepts Identifier for consistency.
+ * @param {import("estree").MemberExpression | import("estree").Identifier} node
+ * @returns {string | null}
+ *
+ * @example `foo` gives "foo"
+ * @example `foo.bar` gives "bar"
+ * @example `foo.bar.baz` gives "baz"
+ */
+ maybeGetMemberPropertyName(node) {
+ if (node.type === "MemberExpression") {
+ return node.property.name;
+ }
+ if (node.type === "Identifier") {
+ return node.name;
+ }
+ return null;
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
new file mode 100644
index 0000000000..a6b7209b6e
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js
@@ -0,0 +1,96 @@
+/**
+ * @fileoverview A collection of rules that help enforce JavaScript coding
+ * standard and avoid common errors in the Mozilla project.
+ * 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/.
+ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Plugin Definition
+// ------------------------------------------------------------------------------
+module.exports = {
+ configs: {
+ "browser-test": require("../lib/configs/browser-test"),
+ "chrome-test": require("../lib/configs/chrome-test"),
+ "mochitest-test": require("../lib/configs/mochitest-test"),
+ recommended: require("../lib/configs/recommended"),
+ "require-jsdoc": require("../lib/configs/require-jsdoc"),
+ "valid-jsdoc": require("../lib/configs/valid-jsdoc"),
+ "xpcshell-test": require("../lib/configs/xpcshell-test"),
+ },
+ environments: {
+ "browser-window": require("../lib/environments/browser-window.js"),
+ "chrome-script": require("../lib/environments/chrome-script.js"),
+ "chrome-worker": require("../lib/environments/chrome-worker.js"),
+ "frame-script": require("../lib/environments/frame-script.js"),
+ jsm: require("../lib/environments/jsm.js"),
+ "process-script": require("../lib/environments/process-script.js"),
+ "remote-page": require("../lib/environments/remote-page.js"),
+ simpletest: require("../lib/environments/simpletest.js"),
+ sjs: require("../lib/environments/sjs.js"),
+ "special-powers-sandbox": require("../lib/environments/special-powers-sandbox.js"),
+ specific: require("../lib/environments/specific"),
+ privileged: require("../lib/environments/privileged.js"),
+ xpcshell: require("../lib/environments/xpcshell.js"),
+ },
+ rules: {
+ "avoid-Date-timing": require("../lib/rules/avoid-Date-timing"),
+ "avoid-removeChild": require("../lib/rules/avoid-removeChild"),
+ "balanced-listeners": require("../lib/rules/balanced-listeners"),
+ "balanced-observers": require("../lib/rules/balanced-observers"),
+ "consistent-if-bracing": require("../lib/rules/consistent-if-bracing"),
+ "import-browser-window-globals": require("../lib/rules/import-browser-window-globals"),
+ "import-content-task-globals": require("../lib/rules/import-content-task-globals"),
+ "import-globals": require("../lib/rules/import-globals"),
+ "import-headjs-globals": require("../lib/rules/import-headjs-globals"),
+ "lazy-getter-object-name": require("../lib/rules/lazy-getter-object-name"),
+ "mark-exported-symbols-as-used": require("../lib/rules/mark-exported-symbols-as-used"),
+ "mark-test-function-used": require("../lib/rules/mark-test-function-used"),
+ "no-aArgs": require("../lib/rules/no-aArgs"),
+ "no-addtask-setup": require("../lib/rules/no-addtask-setup"),
+ "no-arbitrary-setTimeout": require("../lib/rules/no-arbitrary-setTimeout"),
+ "no-compare-against-boolean-literals": require("../lib/rules/no-compare-against-boolean-literals"),
+ "no-cu-reportError": require("../lib/rules/no-cu-reportError"),
+ "no-define-cc-etc": require("../lib/rules/no-define-cc-etc"),
+ "no-throw-cr-literal": require("../lib/rules/no-throw-cr-literal"),
+ "no-useless-parameters": require("../lib/rules/no-useless-parameters"),
+ "no-useless-removeEventListener": require("../lib/rules/no-useless-removeEventListener"),
+ "no-useless-run-test": require("../lib/rules/no-useless-run-test"),
+ "prefer-boolean-length-check": require("../lib/rules/prefer-boolean-length-check"),
+ "prefer-formatValues": require("../lib/rules/prefer-formatValues"),
+ "reject-addtask-only": require("../lib/rules/reject-addtask-only"),
+ "reject-chromeutils-import-params": require("../lib/rules/reject-chromeutils-import-params"),
+ "reject-eager-module-in-lazy-getter": require("../lib/rules/reject-eager-module-in-lazy-getter"),
+ "reject-global-this": require("../lib/rules/reject-global-this"),
+ "reject-globalThis-modification": require("../lib/rules/reject-globalThis-modification"),
+ "reject-import-system-module-from-non-system": require("../lib/rules/reject-import-system-module-from-non-system"),
+ "reject-importGlobalProperties": require("../lib/rules/reject-importGlobalProperties"),
+ "reject-lazy-imports-into-globals": require("../lib/rules/reject-lazy-imports-into-globals"),
+ "reject-mixing-eager-and-lazy": require("../lib/rules/reject-mixing-eager-and-lazy"),
+ "reject-multiple-getters-calls": require("../lib/rules/reject-multiple-getters-calls"),
+ "reject-osfile": require("../lib/rules/reject-osfile"),
+ "reject-scriptableunicodeconverter": require("../lib/rules/reject-scriptableunicodeconverter"),
+ "reject-relative-requires": require("../lib/rules/reject-relative-requires"),
+ "reject-some-requires": require("../lib/rules/reject-some-requires"),
+ "reject-top-level-await": require("../lib/rules/reject-top-level-await"),
+ "rejects-requires-await": require("../lib/rules/rejects-requires-await"),
+ "use-cc-etc": require("../lib/rules/use-cc-etc"),
+ "use-chromeutils-generateqi": require("../lib/rules/use-chromeutils-generateqi"),
+ "use-chromeutils-import": require("../lib/rules/use-chromeutils-import"),
+ "use-default-preference-values": require("../lib/rules/use-default-preference-values"),
+ "use-ownerGlobal": require("../lib/rules/use-ownerGlobal"),
+ "use-includes-instead-of-indexOf": require("../lib/rules/use-includes-instead-of-indexOf"),
+ "use-isInstance": require("./rules/use-isInstance"),
+ "use-returnValue": require("../lib/rules/use-returnValue"),
+ "use-services": require("../lib/rules/use-services"),
+ "use-static-import": require("../lib/rules/use-static-import"),
+ "valid-ci-uses": require("../lib/rules/valid-ci-uses"),
+ "valid-lazy": require("../lib/rules/valid-lazy"),
+ "valid-services": require("../lib/rules/valid-services"),
+ "valid-services-property": require("../lib/rules/valid-services-property"),
+ "var-only-at-top-level": require("../lib/rules/var-only-at-top-level"),
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-Date-timing.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-Date-timing.js
new file mode 100644
index 0000000000..402e1fe7b8
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-Date-timing.js
@@ -0,0 +1,59 @@
+/**
+ * @fileoverview Disallow using Date for timing in performance sensitive code
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/avoid-Date-timing.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let callee = node.callee;
+ if (
+ callee.type !== "MemberExpression" ||
+ callee.object.type !== "Identifier" ||
+ callee.object.name !== "Date" ||
+ callee.property.type !== "Identifier" ||
+ callee.property.name !== "now"
+ ) {
+ return;
+ }
+
+ context.report(
+ node,
+ "use performance.now() instead of Date.now() for timing " +
+ "measurements"
+ );
+ },
+
+ NewExpression(node) {
+ let callee = node.callee;
+ if (
+ callee.type !== "Identifier" ||
+ callee.name !== "Date" ||
+ node.arguments.length
+ ) {
+ return;
+ }
+
+ context.report(
+ node,
+ "use performance.now() instead of new Date() for timing " +
+ "measurements"
+ );
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-removeChild.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-removeChild.js
new file mode 100644
index 0000000000..b356bc1df3
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/avoid-removeChild.js
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Reject using element.parentNode.removeChild(element) when
+ * element.remove() can be used instead.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/avoid-removeChild.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let callee = node.callee;
+ if (
+ callee.type !== "MemberExpression" ||
+ callee.property.type !== "Identifier" ||
+ callee.property.name != "removeChild" ||
+ node.arguments.length != 1
+ ) {
+ return;
+ }
+
+ if (
+ callee.object.type == "MemberExpression" &&
+ callee.object.property.type == "Identifier" &&
+ callee.object.property.name == "parentNode" &&
+ helpers.getASTSource(callee.object.object, context) ==
+ helpers.getASTSource(node.arguments[0])
+ ) {
+ context.report(
+ node,
+ "use element.remove() instead of " +
+ "element.parentNode.removeChild(element)"
+ );
+ }
+
+ if (
+ node.arguments[0].type == "MemberExpression" &&
+ node.arguments[0].property.type == "Identifier" &&
+ node.arguments[0].property.name == "firstChild" &&
+ helpers.getASTSource(callee.object, context) ==
+ helpers.getASTSource(node.arguments[0].object)
+ ) {
+ context.report(
+ node,
+ "use element.firstChild.remove() instead of " +
+ "element.removeChild(element.firstChild)"
+ );
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js
new file mode 100644
index 0000000000..af2930d5eb
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js
@@ -0,0 +1,145 @@
+/**
+ * @fileoverview Check that there's a removeEventListener for each
+ * addEventListener and an off for each on.
+ * Note that for now, this rule is rather simple in that it only checks that
+ * for each event name there is both an add and remove listener. It doesn't
+ * check that these are called on the right objects or with the same callback.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/balanced-listeners.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ var DICTIONARY = {
+ addEventListener: "removeEventListener",
+ on: "off",
+ };
+ // Invert this dictionary to make it easy later.
+ var INVERTED_DICTIONARY = {};
+ for (var i in DICTIONARY) {
+ INVERTED_DICTIONARY[DICTIONARY[i]] = i;
+ }
+
+ // Collect the add/remove listeners in these 2 arrays.
+ var addedListeners = [];
+ var removedListeners = [];
+
+ function addAddedListener(node) {
+ var capture = false;
+ let options = node.arguments[2];
+ if (options) {
+ if (options.type == "ObjectExpression") {
+ if (
+ options.properties.some(
+ p => p.key.name == "once" && p.value.value === true
+ )
+ ) {
+ // No point in adding listeners using the 'once' option.
+ return;
+ }
+ capture = options.properties.some(
+ p => p.key.name == "capture" && p.value.value === true
+ );
+ } else {
+ capture = options.value;
+ }
+ }
+ addedListeners.push({
+ functionName: node.callee.property.name,
+ type: node.arguments[0].value,
+ node: node.callee.property,
+ useCapture: capture,
+ });
+ }
+
+ function addRemovedListener(node) {
+ var capture = false;
+ let options = node.arguments[2];
+ if (options) {
+ if (options.type == "ObjectExpression") {
+ capture = options.properties.some(
+ p => p.key.name == "capture" && p.value.value === true
+ );
+ } else {
+ capture = options.value;
+ }
+ }
+ removedListeners.push({
+ functionName: node.callee.property.name,
+ type: node.arguments[0].value,
+ useCapture: capture,
+ });
+ }
+
+ function getUnbalancedListeners() {
+ var unbalanced = [];
+
+ for (var j = 0; j < addedListeners.length; j++) {
+ if (!hasRemovedListener(addedListeners[j])) {
+ unbalanced.push(addedListeners[j]);
+ }
+ }
+ addedListeners = removedListeners = [];
+
+ return unbalanced;
+ }
+
+ function hasRemovedListener(addedListener) {
+ for (var k = 0; k < removedListeners.length; k++) {
+ var listener = removedListeners[k];
+ if (
+ DICTIONARY[addedListener.functionName] === listener.functionName &&
+ addedListener.type === listener.type &&
+ addedListener.useCapture === listener.useCapture
+ ) {
+ return true;
+ }
+ }
+
+ return false;
+ }
+
+ return {
+ CallExpression(node) {
+ if (node.arguments.length === 0) {
+ return;
+ }
+
+ if (node.callee.type === "MemberExpression") {
+ var listenerMethodName = node.callee.property.name;
+
+ if (DICTIONARY.hasOwnProperty(listenerMethodName)) {
+ addAddedListener(node);
+ } else if (INVERTED_DICTIONARY.hasOwnProperty(listenerMethodName)) {
+ addRemovedListener(node);
+ }
+ }
+ },
+
+ "Program:exit": function() {
+ getUnbalancedListeners().forEach(function(listener) {
+ context.report({
+ node: listener.node,
+ message: "No corresponding '{{functionName}}({{type}})' was found.",
+ data: {
+ functionName: DICTIONARY[listener.functionName],
+ type: listener.type,
+ },
+ });
+ });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-observers.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-observers.js
new file mode 100644
index 0000000000..d75b23de90
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-observers.js
@@ -0,0 +1,118 @@
+/**
+ * @fileoverview Check that there's a Services.(prefs|obs).removeObserver for
+ * each addObserver.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/balanced-observers.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ var addedObservers = [];
+ var removedObservers = [];
+
+ function getObserverAPI(node) {
+ const object = node.callee.object;
+ if (
+ object.type == "MemberExpression" &&
+ object.property.type == "Identifier"
+ ) {
+ return object.property.name;
+ }
+ return null;
+ }
+
+ function isServicesObserver(api) {
+ return api == "obs" || api == "prefs";
+ }
+
+ function getObservableName(node, api) {
+ if (api === "obs") {
+ return node.arguments[1].value;
+ }
+ return node.arguments[0].value;
+ }
+
+ function addAddedObserver(node) {
+ const api = getObserverAPI(node);
+ if (!isServicesObserver(api)) {
+ return;
+ }
+
+ addedObservers.push({
+ functionName: node.callee.property.name,
+ observable: getObservableName(node, api),
+ node: node.callee.property,
+ });
+ }
+
+ function addRemovedObserver(node) {
+ const api = getObserverAPI(node);
+ if (!isServicesObserver(api)) {
+ return;
+ }
+
+ removedObservers.push({
+ functionName: node.callee.property.name,
+ observable: getObservableName(node, api),
+ });
+ }
+
+ function getUnbalancedObservers() {
+ const unbalanced = addedObservers.filter(
+ observer => !hasRemovedObserver(observer)
+ );
+ addedObservers = removedObservers = [];
+
+ return unbalanced;
+ }
+
+ function hasRemovedObserver(addedObserver) {
+ return removedObservers.some(
+ observer => addedObserver.observable === observer.observable
+ );
+ }
+
+ return {
+ CallExpression(node) {
+ if (node.arguments.length === 0) {
+ return;
+ }
+
+ if (node.callee.type === "MemberExpression") {
+ var methodName = node.callee.property.name;
+
+ if (methodName === "addObserver") {
+ addAddedObserver(node);
+ } else if (methodName === "removeObserver") {
+ addRemovedObserver(node);
+ }
+ }
+ },
+
+ "Program:exit": function() {
+ getUnbalancedObservers().forEach(function(observer) {
+ context.report({
+ node: observer.node,
+ message:
+ "No corresponding 'removeObserver(\"{{observable}}\")' was found.",
+ data: {
+ observable: observer.observable,
+ },
+ });
+ });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/consistent-if-bracing.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/consistent-if-bracing.js
new file mode 100644
index 0000000000..d83d0b9b33
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/consistent-if-bracing.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview checks if/else if/else bracing is consistent
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/consistent-if-bracing.html",
+ },
+ messages: {
+ consistentIfBracing: "Bracing of if..else bodies should be consistent.",
+ },
+ type: "layout",
+ },
+
+ create(context) {
+ return {
+ IfStatement(node) {
+ if (node.parent.type !== "IfStatement") {
+ let types = new Set();
+ for (
+ let currentNode = node;
+ currentNode;
+ currentNode = currentNode.alternate
+ ) {
+ let type = currentNode.consequent.type;
+ types.add(type == "BlockStatement" ? "Block" : "NotBlock");
+ if (
+ currentNode.alternate &&
+ currentNode.alternate.type !== "IfStatement"
+ ) {
+ type = currentNode.alternate.type;
+ types.add(type == "BlockStatement" ? "Block" : "NotBlock");
+ break;
+ }
+ }
+ if (types.size > 1) {
+ context.report({
+ node,
+ messageId: "consistentIfBracing",
+ });
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browser-window-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browser-window-globals.js
new file mode 100644
index 0000000000..6f9dcc1861
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browser-window-globals.js
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview For scripts included in browser-window, this will automatically
+ * inject the browser-window global scopes into the file.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var path = require("path");
+var helpers = require("../helpers");
+var browserWindowEnv = require("../environments/browser-window");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/import-browser-window-globals.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ Program(node) {
+ let filePath = helpers.getAbsoluteFilePath(context);
+ let relativePath = path.relative(helpers.rootDir, filePath);
+ // We need to translate the path on Windows, due to the change
+ // from \ to /, and browserjsScripts assumes Posix.
+ if (path.win32) {
+ relativePath = relativePath.split(path.sep).join("/");
+ }
+
+ if (browserWindowEnv.browserjsScripts?.includes(relativePath)) {
+ for (let global in browserWindowEnv.globals) {
+ helpers.addVarToScope(
+ global,
+ context.getScope(),
+ browserWindowEnv.globals[global]
+ );
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-content-task-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-content-task-globals.js
new file mode 100644
index 0000000000..823f8d5532
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-content-task-globals.js
@@ -0,0 +1,75 @@
+/**
+ * @fileoverview For ContentTask.spawn, this will automatically declare the
+ * frame script variables in the global scope.
+ * Note: due to the way ESLint works, it appears it is only
+ * easy to declare these variables on a file-global scope, rather
+ * than function global.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+var frameScriptEnv = require("../environments/frame-script");
+var sandboxEnv = require("../environments/special-powers-sandbox");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/import-content-task-globals.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ "CallExpression[callee.object.name='ContentTask'][callee.property.name='spawn']": function(
+ node
+ ) {
+ // testing/mochitest/BrowserTestUtils/content/content-task.js
+ // This script is loaded as a sub script into a frame script.
+ for (let [name, value] of Object.entries(frameScriptEnv.globals)) {
+ helpers.addVarToScope(name, context.getScope(), value);
+ }
+ },
+ "CallExpression[callee.object.name='SpecialPowers'][callee.property.name='spawn']": function(
+ node
+ ) {
+ for (let [name, value] of Object.entries(sandboxEnv.globals)) {
+ helpers.addVarToScope(name, context.getScope(), value);
+ }
+ let globals = [
+ // testing/specialpowers/content/SpecialPowersChild.sys.mjs
+ // SpecialPowersChild._spawnTask
+ "SpecialPowers",
+ "ContentTaskUtils",
+ "content",
+ "docShell",
+ ];
+ for (let global of globals) {
+ helpers.addVarToScope(global, context.getScope(), false);
+ }
+ },
+ "CallExpression[callee.object.name='SpecialPowers'][callee.property.name='spawnChrome']": function(
+ node
+ ) {
+ for (let [name, value] of Object.entries(sandboxEnv.globals)) {
+ helpers.addVarToScope(name, context.getScope(), value);
+ }
+ let globals = [
+ // testing/specialpowers/content/SpecialPowersParent.sys.mjs
+ // SpecialPowersParent._spawnChrome
+ "windowGlobalParent",
+ "browsingContext",
+ ];
+ for (let global of globals) {
+ helpers.addVarToScope(global, context.getScope(), false);
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js
new file mode 100644
index 0000000000..9153e04fd6
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js
@@ -0,0 +1,21 @@
+/**
+ * @fileoverview Discovers all globals for the current file.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/import-globals.html",
+ },
+ type: "problem",
+ },
+
+ create: require("../globals").getESLintGlobalParser,
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
new file mode 100644
index 0000000000..a1f21dc784
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js
@@ -0,0 +1,49 @@
+/**
+ * @fileoverview Import globals from head.js and from any files that were
+ * imported by head.js (as far as we can correctly resolve the path).
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var fs = require("fs");
+var helpers = require("../helpers");
+var globals = require("../globals");
+
+function importHead(context, path, node) {
+ try {
+ let stats = fs.statSync(path);
+ if (!stats.isFile()) {
+ return;
+ }
+ } catch (e) {
+ return;
+ }
+
+ let newGlobals = globals.getGlobalsForFile(path);
+ helpers.addGlobals(newGlobals, context.getScope());
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/import-headjs-globals.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ Program(node) {
+ let heads = helpers.getTestHeadFiles(context);
+ for (let head of heads) {
+ importHead(context, head, node);
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/lazy-getter-object-name.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/lazy-getter-object-name.js
new file mode 100644
index 0000000000..4b4e807140
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/lazy-getter-object-name.js
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Enforce the standard object name for
+ * ChromeUtils.defineESModuleGetters
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isIdentifier(node, id) {
+ return node.type === "Identifier" && node.name === id;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/lazy-getter-object-name.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let { callee } = node;
+ if (
+ callee.type === "MemberExpression" &&
+ isIdentifier(callee.object, "ChromeUtils") &&
+ isIdentifier(callee.property, "defineESModuleGetters") &&
+ node.arguments.length >= 1 &&
+ !isIdentifier(node.arguments[0], "lazy")
+ ) {
+ context.report({
+ node,
+ message:
+ "The variable name of the object passed to ChromeUtils.defineESModuleGetters must be `lazy`",
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-exported-symbols-as-used.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-exported-symbols-as-used.js
new file mode 100644
index 0000000000..2fcfe8390a
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-exported-symbols-as-used.js
@@ -0,0 +1,85 @@
+/**
+ * @fileoverview Simply marks exported symbols as used. Designed for use in
+ * .jsm files only.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function markArrayElementsAsUsed(context, node, expression) {
+ if (expression.type != "ArrayExpression") {
+ context.report({
+ node,
+ message: "Unexpected assignment of non-Array to EXPORTED_SYMBOLS",
+ });
+ return;
+ }
+
+ for (let element of expression.elements) {
+ context.markVariableAsUsed(element.value);
+ }
+ // Also mark EXPORTED_SYMBOLS as used.
+ context.markVariableAsUsed("EXPORTED_SYMBOLS");
+}
+
+// Ignore assignments not in the global scope, e.g. where special module
+// definitions are required due to having different ways of importing files,
+// e.g. osfile.
+function isGlobalScope(context) {
+ return !context.getScope().upper;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/mark-exported-symbols-as-used.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ AssignmentExpression(node, parents) {
+ if (
+ node.operator === "=" &&
+ node.left.type === "MemberExpression" &&
+ node.left.object.type === "ThisExpression" &&
+ node.left.property.name === "EXPORTED_SYMBOLS" &&
+ isGlobalScope(context)
+ ) {
+ markArrayElementsAsUsed(context, node, node.right);
+ }
+ },
+
+ VariableDeclaration(node, parents) {
+ if (!isGlobalScope(context)) {
+ return;
+ }
+
+ for (let item of node.declarations) {
+ if (
+ item.id &&
+ item.id.type == "Identifier" &&
+ item.id.name === "EXPORTED_SYMBOLS"
+ ) {
+ if (node.kind === "let") {
+ // The use of 'let' isn't allowed as the lexical scope may die after
+ // the script executes.
+ context.report({
+ node,
+ message:
+ "EXPORTED_SYMBOLS cannot be declared via `let`. Use `var` or `this.EXPORTED_SYMBOLS =`",
+ });
+ }
+
+ markArrayElementsAsUsed(context, node, item.init);
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js
new file mode 100644
index 0000000000..9913763ad9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js
@@ -0,0 +1,42 @@
+/**
+ * @fileoverview Simply marks `test` (the test method) or `run_test` as used
+ * when in mochitests or xpcshell tests respectively. This avoids ESLint telling
+ * us that the function is never called.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/mark-test-function-used.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ Program() {
+ let testType = helpers.getTestType(context);
+ if (testType == "browser") {
+ context.markVariableAsUsed("test");
+ }
+
+ if (testType == "xpcshell") {
+ context.markVariableAsUsed("run_test");
+ }
+
+ if (helpers.getIsSjs(context)) {
+ context.markVariableAsUsed("handleRequest");
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js
new file mode 100644
index 0000000000..9ae8f5005a
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js
@@ -0,0 +1,54 @@
+/**
+ * @fileoverview warns against using hungarian notation in function arguments
+ * (i.e. aArg).
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isPrefixed(name) {
+ return name.length >= 2 && /^a[A-Z]/.test(name);
+}
+
+function deHungarianize(name) {
+ return name.substring(1, 2).toLowerCase() + name.substring(2, name.length);
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-aArgs.html",
+ },
+ type: "layout",
+ },
+
+ create(context) {
+ function checkFunction(node) {
+ for (var i = 0; i < node.params.length; i++) {
+ var param = node.params[i];
+ if (param.name && isPrefixed(param.name)) {
+ var errorObj = {
+ name: param.name,
+ suggestion: deHungarianize(param.name),
+ };
+ context.report(
+ param,
+ "Parameter '{{name}}' uses Hungarian Notation, " +
+ "consider using '{{suggestion}}' instead.",
+ errorObj
+ );
+ }
+ }
+ }
+
+ return {
+ FunctionDeclaration: checkFunction,
+ ArrowFunctionExpression: checkFunction,
+ FunctionExpression: checkFunction,
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js
new file mode 100644
index 0000000000..58e70b862d
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-addtask-setup.js
@@ -0,0 +1,51 @@
+/**
+ * @fileoverview Reject `add_task(async function setup` or similar patterns in
+ * favour of add_setup.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isNamedLikeSetup(name) {
+ return /^(init|setup)$/i.test(name);
+}
+
+module.exports = {
+ meta: {
+ type: "suggestion",
+ fixable: "code",
+ },
+ create(context) {
+ return {
+ "Program > ExpressionStatement > CallExpression": function(node) {
+ let callee = node.callee;
+ if (callee.type === "Identifier" && callee.name === "add_task") {
+ let arg = node.arguments[0];
+ if (
+ arg.type !== "FunctionExpression" ||
+ !arg.id ||
+ !isNamedLikeSetup(arg.id.name)
+ ) {
+ return;
+ }
+ context.report({
+ node,
+ message:
+ "Do not use add_task() for setup, use add_setup() instead.",
+ fix: fixer => {
+ let range = [node.callee.range[0], arg.id.range[1]];
+ let asyncOrNot = arg.async ? "async " : "";
+ return fixer.replaceTextRange(
+ range,
+ `add_setup(${asyncOrNot}function`
+ );
+ },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-arbitrary-setTimeout.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-arbitrary-setTimeout.js
new file mode 100644
index 0000000000..2ed3dc83a7
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-arbitrary-setTimeout.js
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Reject use of non-zero values in setTimeout
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+var testTypes = new Set(["browser", "xpcshell"]);
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-arbitrary-setTimeout.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ // We don't want to run this on mochitest plain as it already
+ // prevents flaky setTimeout at runtime. This check is built-in
+ // to the rule itself as sometimes other tests can live alongside
+ // plain mochitests and so it can't be configured via eslintrc.
+ if (!testTypes.has(helpers.getTestType(context))) {
+ return {};
+ }
+
+ return {
+ CallExpression(node) {
+ let callee = node.callee;
+ if (callee.type === "MemberExpression") {
+ if (
+ callee.property.name !== "setTimeout" ||
+ callee.object.name !== "window" ||
+ node.arguments.length < 2
+ ) {
+ return;
+ }
+ } else if (callee.type === "Identifier") {
+ if (callee.name !== "setTimeout" || node.arguments.length < 2) {
+ return;
+ }
+ } else {
+ return;
+ }
+
+ let timeout = node.arguments[1];
+ if (timeout.type !== "Literal" || timeout.value > 0) {
+ context.report(
+ node,
+ "listen for events instead of setTimeout() " +
+ "with arbitrary delay"
+ );
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-compare-against-boolean-literals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-compare-against-boolean-literals.js
new file mode 100644
index 0000000000..f739961119
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-compare-against-boolean-literals.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Restrict comparing against `true` or `false`.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-compare-against-boolean-literals.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ BinaryExpression(node) {
+ if (
+ ["==", "!="].includes(node.operator) &&
+ (["true", "false"].includes(node.left.raw) ||
+ ["true", "false"].includes(node.right.raw))
+ ) {
+ context.report(
+ node,
+ "Don't compare for inexact equality against boolean literals"
+ );
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cu-reportError.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cu-reportError.js
new file mode 100644
index 0000000000..f62fa5667f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cu-reportError.js
@@ -0,0 +1,135 @@
+/**
+ * @fileoverview Reject common XPCOM methods called with useless optional
+ * parameters, or non-existent parameters.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isCuReportError(node) {
+ return (
+ node.type == "MemberExpression" &&
+ node.object.type == "Identifier" &&
+ node.object.name == "Cu" &&
+ node.property.type == "Identifier" &&
+ node.property.name == "reportError"
+ );
+}
+
+function isConcatenation(node) {
+ return node.type == "BinaryExpression" && node.operator == "+";
+}
+
+function isIdentOrMember(node) {
+ return node.type == "MemberExpression" || node.type == "Identifier";
+}
+
+function isLiteralOrConcat(node) {
+ return node.type == "Literal" || isConcatenation(node);
+}
+
+function replaceConcatWithComma(fixer, node) {
+ let fixes = [];
+ let didFixTrailingIdentifier = false;
+ let recursiveFixes;
+ let trailingIdentifier;
+ // Deal with recursion first:
+ if (isConcatenation(node.right)) {
+ // Uh oh. If the RHS is a concatenation, there are parens involved,
+ // e.g.:
+ // console.error("literal" + (b + "literal"));
+ // It's pretty much impossible to guess what to do here so bail out:
+ return { fixes: [], trailingIdentifier: false };
+ }
+ if (isConcatenation(node.left)) {
+ ({ fixes: recursiveFixes, trailingIdentifier } = replaceConcatWithComma(
+ fixer,
+ node.left
+ ));
+ fixes.push(...recursiveFixes);
+ }
+ // If the left is an identifier or memberexpression, and the right is a
+ // literal or concatenation - or vice versa - replace a + with a comma:
+ if (
+ (isIdentOrMember(node.left) && isLiteralOrConcat(node.right)) ||
+ (isIdentOrMember(node.right) && isLiteralOrConcat(node.left)) ||
+ // Or if the rhs is a literal/concatenation, while the right-most part of
+ // the lhs is also an identifier (need 2 commas either side!)
+ (trailingIdentifier && isLiteralOrConcat(node.right))
+ ) {
+ fixes.push(
+ fixer.replaceTextRange([node.left.range[1], node.right.range[0]], ", ")
+ );
+ didFixTrailingIdentifier = isIdentOrMember(node.right);
+ }
+ return { fixes, trailingIdentifier: didFixTrailingIdentifier };
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-cu-reportError.html",
+ },
+ fixable: "code",
+ messages: {
+ useConsoleError: "Please use console.error instead of Cu.reportError",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let checkNode;
+ if (
+ node.arguments.length >= 1 &&
+ node.arguments[0].type == "MemberExpression"
+ ) {
+ // Handles cases of `.foo(Cu.reportError)`.
+ checkNode = node.arguments[0];
+ } else {
+ // Handles cases of `Cu.reportError()`.
+ checkNode = node.callee;
+ }
+ if (!isCuReportError(checkNode)) {
+ return;
+ }
+
+ if (checkNode == node.callee && node.arguments.length > 1) {
+ // TODO: Bug 1802347 For initial landing, we allow the two
+ // argument form of Cu.reportError as the second argument is a stack
+ // argument which is more complicated to deal with.
+ return;
+ }
+
+ context.report({
+ node,
+ fix: fixer => {
+ let fixes = [
+ fixer.replaceText(checkNode.object, "console"),
+ fixer.replaceText(checkNode.property, "error"),
+ ];
+ // If we're adding stuff together as an argument, split
+ // into multiple arguments instead:
+ if (
+ checkNode == node.callee &&
+ isConcatenation(node.arguments[0])
+ ) {
+ let { fixes: recursiveFixes } = replaceConcatWithComma(
+ fixer,
+ node.arguments[0]
+ );
+ fixes.push(...recursiveFixes);
+ }
+ return fixes;
+ },
+ messageId: "useConsoleError",
+ });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-define-cc-etc.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-define-cc-etc.js
new file mode 100644
index 0000000000..2d336d2470
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-define-cc-etc.js
@@ -0,0 +1,51 @@
+/**
+ * @fileoverview Reject defining Cc/Ci/Cr/Cu.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const componentsBlacklist = ["Cc", "Ci", "Cr", "Cu"];
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-define-cc-etc.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ VariableDeclarator(node) {
+ if (
+ node.id.type == "Identifier" &&
+ componentsBlacklist.includes(node.id.name)
+ ) {
+ context.report(
+ node,
+ `${node.id.name} is now defined in global scope, a separate definition is no longer necessary.`
+ );
+ }
+
+ if (node.id.type == "ObjectPattern") {
+ for (let property of node.id.properties) {
+ if (
+ property.type == "Property" &&
+ componentsBlacklist.includes(property.value.name)
+ ) {
+ context.report(
+ node,
+ `${property.value.name} is now defined in global scope, a separate definition is no longer necessary.`
+ );
+ }
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-throw-cr-literal.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-throw-cr-literal.js
new file mode 100644
index 0000000000..175b6e254c
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-throw-cr-literal.js
@@ -0,0 +1,101 @@
+/**
+ * @fileoverview Rule to prevent throwing bare Cr.ERRORs.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isCr(object) {
+ return object.type === "Identifier" && object.name === "Cr";
+}
+
+function isComponentsResults(object) {
+ return (
+ object.type === "MemberExpression" &&
+ object.object.type === "Identifier" &&
+ object.object.name === "Components" &&
+ object.property.type === "Identifier" &&
+ object.property.name === "results"
+ );
+}
+
+function isNewError(argument) {
+ return (
+ argument.type === "NewExpression" &&
+ argument.callee.type === "Identifier" &&
+ argument.callee.name === "Error" &&
+ argument.arguments.length === 1
+ );
+}
+
+function fixT(context, node, argument, fixer) {
+ const sourceText = context.getSourceCode().getText(argument);
+ return fixer.replaceText(node, `Components.Exception("", ${sourceText})`);
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-throw-cr-literal.html",
+ },
+ fixable: "code",
+ messages: {
+ bareCR: "Do not throw bare Cr.ERRORs, use Components.Exception instead",
+ bareComponentsResults:
+ "Do not throw bare Components.results.ERRORs, use Components.Exception instead",
+ newErrorCR:
+ "Do not pass Cr.ERRORs to new Error(), use Components.Exception instead",
+ newErrorComponentsResults:
+ "Do not pass Components.results.ERRORs to new Error(), use Components.Exception instead",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ ThrowStatement(node) {
+ if (node.argument.type === "MemberExpression") {
+ const fix = fixT.bind(null, context, node.argument, node.argument);
+
+ if (isCr(node.argument.object)) {
+ context.report({
+ node,
+ messageId: "bareCR",
+ fix,
+ });
+ } else if (isComponentsResults(node.argument.object)) {
+ context.report({
+ node,
+ messageId: "bareComponentsResults",
+ fix,
+ });
+ }
+ } else if (isNewError(node.argument)) {
+ const argument = node.argument.arguments[0];
+
+ if (argument.type === "MemberExpression") {
+ const fix = fixT.bind(null, context, node.argument, argument);
+
+ if (isCr(argument.object)) {
+ context.report({
+ node,
+ messageId: "newErrorCR",
+ fix,
+ });
+ } else if (isComponentsResults(argument.object)) {
+ context.report({
+ node,
+ messageId: "newErrorComponentsResults",
+ fix,
+ });
+ }
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-parameters.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-parameters.js
new file mode 100644
index 0000000000..a2f7e77d16
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-parameters.js
@@ -0,0 +1,144 @@
+/**
+ * @fileoverview Reject common XPCOM methods called with useless optional
+ * parameters, or non-existent parameters.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-parameters.html",
+ },
+ fixable: "code",
+ type: "suggestion",
+ },
+
+ create(context) {
+ function getRangeAfterArgToEnd(argNumber, args) {
+ let sourceCode = context.getSourceCode();
+ return [
+ sourceCode.getTokenAfter(args[argNumber]).range[0],
+ args[args.length - 1].range[1],
+ ];
+ }
+
+ return {
+ CallExpression(node) {
+ let callee = node.callee;
+ if (
+ callee.type !== "MemberExpression" ||
+ callee.property.type !== "Identifier"
+ ) {
+ return;
+ }
+
+ let isFalse = arg => arg.type === "Literal" && arg.value === false;
+ let isFalsy = arg => arg.type === "Literal" && !arg.value;
+ let isBool = arg =>
+ arg.type === "Literal" && (arg.value === false || arg.value === true);
+ let name = callee.property.name;
+ let args = node.arguments;
+
+ if (
+ ["addEventListener", "removeEventListener", "addObserver"].includes(
+ name
+ ) &&
+ args.length === 3 &&
+ isFalse(args[2])
+ ) {
+ context.report({
+ node,
+ fix: fixer => {
+ return fixer.removeRange(getRangeAfterArgToEnd(1, args));
+ },
+ message: `${name}'s third parameter can be omitted when it's false.`,
+ });
+ }
+
+ if (name === "clearUserPref" && args.length > 1) {
+ context.report({
+ node,
+ fix: fixer => {
+ return fixer.removeRange(getRangeAfterArgToEnd(0, args));
+ },
+ message: `${name} takes only 1 parameter.`,
+ });
+ }
+
+ if (name === "removeObserver" && args.length === 3 && isBool(args[2])) {
+ context.report({
+ node,
+ fix: fixer => {
+ return fixer.removeRange(getRangeAfterArgToEnd(1, args));
+ },
+ message: "removeObserver only takes 2 parameters.",
+ });
+ }
+
+ if (name === "appendElement" && args.length === 2 && isFalse(args[1])) {
+ context.report({
+ node,
+ fix: fixer => {
+ return fixer.removeRange(getRangeAfterArgToEnd(0, args));
+ },
+ message: `${name}'s second parameter can be omitted when it's false.`,
+ });
+ }
+
+ if (
+ name === "notifyObservers" &&
+ args.length === 3 &&
+ isFalsy(args[2])
+ ) {
+ context.report({
+ node,
+ fix: fixer => {
+ return fixer.removeRange(getRangeAfterArgToEnd(1, args));
+ },
+ message: `${name}'s third parameter can be omitted.`,
+ });
+ }
+
+ if (
+ name === "getComputedStyle" &&
+ args.length === 2 &&
+ isFalsy(args[1])
+ ) {
+ context.report({
+ node,
+ fix: fixer => {
+ return fixer.removeRange(getRangeAfterArgToEnd(0, args));
+ },
+ message: "getComputedStyle's second parameter can be omitted.",
+ });
+ }
+
+ if (
+ name === "newURI" &&
+ args.length > 1 &&
+ isFalsy(args[args.length - 1])
+ ) {
+ context.report({
+ node,
+ fix: fixer => {
+ if (args.length > 2 && isFalsy(args[args.length - 2])) {
+ return fixer.removeRange(getRangeAfterArgToEnd(0, args));
+ }
+
+ return fixer.removeRange(
+ getRangeAfterArgToEnd(args.length - 2, args)
+ );
+ },
+ message: "newURI's last parameters are optional.",
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-removeEventListener.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-removeEventListener.js
new file mode 100644
index 0000000000..74c4fed43b
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-removeEventListener.js
@@ -0,0 +1,66 @@
+/**
+ * @fileoverview Reject calls to removeEventListenter where {once: true} could
+ * be used instead.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-removeEventListener.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let callee = node.callee;
+ if (
+ callee.type !== "MemberExpression" ||
+ callee.property.type !== "Identifier" ||
+ callee.property.name !== "addEventListener" ||
+ node.arguments.length == 4
+ ) {
+ return;
+ }
+
+ let listener = node.arguments[1];
+ if (
+ !listener ||
+ listener.type != "FunctionExpression" ||
+ !listener.body ||
+ listener.body.type != "BlockStatement" ||
+ !listener.body.body.length ||
+ listener.body.body[0].type != "ExpressionStatement" ||
+ listener.body.body[0].expression.type != "CallExpression"
+ ) {
+ return;
+ }
+
+ let call = listener.body.body[0].expression;
+ if (
+ call.callee.type == "MemberExpression" &&
+ call.callee.property.type == "Identifier" &&
+ call.callee.property.name == "removeEventListener" &&
+ ((call.arguments[0].type == "Literal" &&
+ call.arguments[0].value == node.arguments[0].value) ||
+ (call.arguments[0].type == "Identifier" &&
+ call.arguments[0].name == node.arguments[0].name))
+ ) {
+ context.report(
+ call,
+ "use {once: true} instead of removeEventListener as " +
+ "the first instruction of the listener"
+ );
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-run-test.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-run-test.js
new file mode 100644
index 0000000000..51ac797ee2
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-useless-run-test.js
@@ -0,0 +1,73 @@
+/**
+ * @fileoverview Reject run_test() definitions where they aren't necessary.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/no-useless-run-test.html",
+ },
+ fixable: "code",
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ "Program > FunctionDeclaration": function(node) {
+ if (
+ node.id.name === "run_test" &&
+ node.body.type === "BlockStatement" &&
+ node.body.body.length === 1 &&
+ node.body.body[0].type === "ExpressionStatement" &&
+ node.body.body[0].expression.type === "CallExpression" &&
+ node.body.body[0].expression.callee.name === "run_next_test"
+ ) {
+ context.report({
+ node,
+ fix: fixer => {
+ let sourceCode = context.getSourceCode();
+ let startNode;
+ if (sourceCode.getCommentsBefore) {
+ // ESLint 4 has getCommentsBefore.
+ startNode = sourceCode.getCommentsBefore(node);
+ } else if (node && node.body && node.leadingComments) {
+ // This is for ESLint 3.
+ startNode = node.leadingComments;
+ }
+
+ // If we have comments, we want the start node to be the comments,
+ // rather than the token before the comments, so that we don't
+ // remove the comments - for run_test, these are likely to be useful
+ // information about the test.
+ if (startNode?.length) {
+ startNode = startNode[startNode.length - 1];
+ } else {
+ startNode = sourceCode.getTokenBefore(node);
+ }
+
+ return fixer.removeRange([
+ // If there's no startNode, we fall back to zero, i.e. start of
+ // file.
+ startNode ? startNode.range[1] + 1 : 0,
+ // We know the function is a block and it'll end with }. Normally
+ // there's a new line after that, so just advance past it. This
+ // may be slightly not dodgy in some cases, but covers the existing
+ // cases.
+ node.range[1] + 1,
+ ]);
+ },
+ message:
+ "Useless run_test function - only contains run_next_test; whole function can be removed",
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-boolean-length-check.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-boolean-length-check.js
new file mode 100644
index 0000000000..81e4bb230a
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-boolean-length-check.js
@@ -0,0 +1,126 @@
+/**
+ * @fileoverview Prefer boolean length check
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function funcForBooleanLength(context, node, conditionCheck) {
+ let newText = "";
+ const sourceCode = context.getSourceCode();
+ switch (node.operator) {
+ case ">":
+ if (node.right.value == 0) {
+ if (conditionCheck) {
+ newText = sourceCode.getText(node.left);
+ } else {
+ newText = "!!" + sourceCode.getText(node.left);
+ }
+ } else {
+ newText = "!" + sourceCode.getText(node.right);
+ }
+ break;
+ case "<":
+ if (node.right.value == 0) {
+ newText = "!" + sourceCode.getText(node.left);
+ } else if (conditionCheck) {
+ newText = sourceCode.getText(node.right);
+ } else {
+ newText = "!!" + sourceCode.getText(node.right);
+ }
+ break;
+ case "==":
+ if (node.right.value == 0) {
+ newText = "!" + sourceCode.getText(node.left);
+ } else {
+ newText = "!" + sourceCode.getText(node.right);
+ }
+ break;
+ case "!=":
+ if (node.right.value == 0) {
+ if (conditionCheck) {
+ newText = sourceCode.getText(node.left);
+ } else {
+ newText = "!!" + sourceCode.getText(node.left);
+ }
+ } else if (conditionCheck) {
+ newText = sourceCode.getText(node.right);
+ } else {
+ newText = "!!" + sourceCode.getText(node.right);
+ }
+ break;
+ }
+ return newText;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/prefer-boolean-length-check.html",
+ },
+ fixable: "code",
+ type: "suggestion",
+ },
+
+ create(context) {
+ const conditionStatement = [
+ "IfStatement",
+ "WhileStatement",
+ "DoWhileStatement",
+ "ForStatement",
+ "ForInStatement",
+ "ConditionalExpression",
+ ];
+
+ return {
+ BinaryExpression(node) {
+ if (
+ ["==", "!=", ">", "<"].includes(node.operator) &&
+ ((node.right.type == "Literal" &&
+ node.right.value == 0 &&
+ node.left.property?.name == "length") ||
+ (node.left.type == "Literal" &&
+ node.left.value == 0 &&
+ node.right.property?.name == "length"))
+ ) {
+ if (
+ conditionStatement.includes(node.parent.type) ||
+ (node.parent.type == "LogicalExpression" &&
+ conditionStatement.includes(node.parent.parent.type))
+ ) {
+ context.report({
+ node,
+ fix: fixer => {
+ let generateExpression = funcForBooleanLength(
+ context,
+ node,
+ true
+ );
+
+ return fixer.replaceText(node, generateExpression);
+ },
+ message: "Prefer boolean length check",
+ });
+ } else {
+ context.report({
+ node,
+ fix: fixer => {
+ let generateExpression = funcForBooleanLength(
+ context,
+ node,
+ false
+ );
+ return fixer.replaceText(node, generateExpression);
+ },
+ message: "Prefer boolean length check",
+ });
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-formatValues.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-formatValues.js
new file mode 100644
index 0000000000..97e3338ec9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/prefer-formatValues.js
@@ -0,0 +1,89 @@
+/**
+ * @fileoverview Reject multiple calls to document.l10n.formatValue in the same
+ * code block.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+/**
+ * As we enter blocks new sets are pushed onto this stack and then popped when
+ * we exit the block.
+ */
+const BlockStack = [];
+
+module.exports = {
+ meta: {
+ docs: {
+ description: "disallow multiple document.l10n.formatValue calls",
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/prefer-formatValues.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ function enterBlock() {
+ BlockStack.push(new Set());
+ }
+
+ function exitBlock() {
+ let calls = BlockStack.pop();
+ if (calls.size > 1) {
+ for (let callNode of calls) {
+ context.report(
+ callNode,
+ "prefer to use a single document.l10n.formatValues call instead " +
+ "of multiple calls to document.l10n.formatValue or document.l10n.formatValues"
+ );
+ }
+ }
+ }
+
+ return {
+ Program: enterBlock,
+ "Program:exit": exitBlock,
+ BlockStatement: enterBlock,
+ "BlockStatement:exit": exitBlock,
+
+ CallExpression(node) {
+ if (!BlockStack.length) {
+ context.report(node, "call expression found outside of known block");
+ }
+
+ let callee = node.callee;
+ if (callee.type !== "MemberExpression") {
+ return;
+ }
+
+ if (
+ !isIdentifier(callee.property, "formatValue") &&
+ !isIdentifier(callee.property, "formatValues")
+ ) {
+ return;
+ }
+
+ if (callee.object.type !== "MemberExpression") {
+ return;
+ }
+
+ if (
+ !isIdentifier(callee.object.object, "document") ||
+ !isIdentifier(callee.object.property, "l10n")
+ ) {
+ return;
+ }
+
+ let calls = BlockStack[BlockStack.length - 1];
+ calls.add(node);
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-addtask-only.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-addtask-only.js
new file mode 100644
index 0000000000..4211d471c8
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-addtask-only.js
@@ -0,0 +1,48 @@
+/**
+ * @fileoverview Don't allow only() in tests
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-addtask-only.html",
+ },
+ hasSuggestions: true,
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (
+ ["add_task", "decorate_task"].includes(
+ node.callee.object?.callee?.name
+ ) &&
+ node.callee.property?.name == "only"
+ ) {
+ context.report({
+ node,
+ message: `add_task(...).only() not allowed - add an exception if this is intentional`,
+ suggest: [
+ {
+ desc: "Remove only() call from task",
+ fix: fixer =>
+ fixer.replaceTextRange(
+ [node.callee.object.range[1], node.range[1]],
+ ""
+ ),
+ },
+ ],
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-chromeutils-import-params.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-chromeutils-import-params.js
new file mode 100644
index 0000000000..f1f4292d8f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-chromeutils-import-params.js
@@ -0,0 +1,62 @@
+/**
+ * @fileoverview Reject calls to ChromeUtils.import(..., null). This allows to
+ * retrieve the global object for the JSM, instead we should rely on explicitly
+ * exported symbols.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+function getRangeAfterArgToEnd(context, argNumber, args) {
+ let sourceCode = context.getSourceCode();
+ return [
+ sourceCode.getTokenAfter(args[argNumber]).range[0],
+ args[args.length - 1].range[1],
+ ];
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-chromeutils-import-params.html",
+ },
+ hasSuggestions: true,
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let { callee } = node;
+ if (
+ isIdentifier(callee.object, "ChromeUtils") &&
+ isIdentifier(callee.property, "import") &&
+ node.arguments.length >= 2
+ ) {
+ context.report({
+ node,
+ message: "ChromeUtils.import only takes one argument.",
+ suggest: [
+ {
+ desc: "Remove the unnecessary parameters.",
+ fix: fixer => {
+ return fixer.removeRange(
+ getRangeAfterArgToEnd(context, 0, node.arguments)
+ );
+ },
+ },
+ ],
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-eager-module-in-lazy-getter.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-eager-module-in-lazy-getter.js
new file mode 100644
index 0000000000..4b0f1f7b48
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-eager-module-in-lazy-getter.js
@@ -0,0 +1,103 @@
+/**
+ * @fileoverview Reject use of lazy getters for modules that's loaded early in
+ * the startup process and not necessarily be lazy.
+ *
+ * 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/.
+ */
+
+"use strict";
+const helpers = require("../helpers");
+
+function isString(node) {
+ return node.type === "Literal" && typeof node.value === "string";
+}
+
+function isEagerModule(resourceURI) {
+ return [
+ "resource://gre/modules/Services",
+ "resource://gre/modules/XPCOMUtils",
+ "resource://gre/modules/AppConstants",
+ ].includes(resourceURI.replace(/(\.jsm|\.jsm\.js|\.js|\.sys\.mjs)$/, ""));
+}
+
+function checkEagerModule(context, node, resourceURI) {
+ if (!isEagerModule(resourceURI)) {
+ return;
+ }
+ context.report({
+ node,
+ messageId: "eagerModule",
+ data: { uri: resourceURI },
+ });
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-eager-module-in-lazy-getter.html",
+ },
+ messages: {
+ eagerModule:
+ 'Module "{{uri}}" is known to be loaded early in the startup process, and should be loaded eagerly, instead of defining a lazy getter.',
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (node.callee.type !== "MemberExpression") {
+ return;
+ }
+
+ let callerSource;
+ try {
+ callerSource = helpers.getASTSource(node.callee);
+ } catch (e) {
+ return;
+ }
+
+ if (
+ callerSource === "XPCOMUtils.defineLazyModuleGetter" ||
+ callerSource === "ChromeUtils.defineModuleGetter"
+ ) {
+ if (node.arguments.length < 3) {
+ return;
+ }
+ const resourceURINode = node.arguments[2];
+ if (!isString(resourceURINode)) {
+ return;
+ }
+ checkEagerModule(context, node, resourceURINode.value);
+ } else if (
+ callerSource === "XPCOMUtils.defineLazyModuleGetters" ||
+ callerSource === "ChromeUtils.defineESModuleGetters"
+ ) {
+ if (node.arguments.length < 2) {
+ return;
+ }
+ const obj = node.arguments[1];
+ if (obj.type !== "ObjectExpression") {
+ return;
+ }
+ for (let prop of obj.properties) {
+ if (prop.type !== "Property") {
+ continue;
+ }
+ if (prop.kind !== "init") {
+ continue;
+ }
+ const resourceURINode = prop.value;
+ if (!isString(resourceURINode)) {
+ continue;
+ }
+ checkEagerModule(context, node, resourceURINode.value);
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-global-this.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-global-this.js
new file mode 100644
index 0000000000..85a559971a
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-global-this.js
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Reject attempts to use the global object in jsms.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const helpers = require("../helpers");
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-global-this.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ ThisExpression(node) {
+ if (!helpers.getIsGlobalThis(context.getAncestors())) {
+ return;
+ }
+
+ context.report({
+ node,
+ message: `JSM should not use the global this`,
+ });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-globalThis-modification.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-globalThis-modification.js
new file mode 100644
index 0000000000..11c7261014
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-globalThis-modification.js
@@ -0,0 +1,70 @@
+/**
+ * @fileoverview Enforce the standard object name for
+ * ChromeUtils.defineESMGetters
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isIdentifier(node, id) {
+ return node.type === "Identifier" && node.name === id;
+}
+
+function calleeToString(node) {
+ if (node.type === "Identifier") {
+ return node.name;
+ }
+
+ if (node.type === "MemberExpression" && !node.computed) {
+ return calleeToString(node.object) + "." + node.property.name;
+ }
+
+ return "???";
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-globalThis-modification.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ AssignmentExpression(node, parents) {
+ let target = node.left;
+ while (target.type === "MemberExpression") {
+ target = target.object;
+ }
+ if (isIdentifier(target, "globalThis")) {
+ context.report({
+ node,
+ message:
+ "`globalThis` shouldn't be modified. `globalThis` is the shared global inside the system module, and properties defined on it is visible from all modules.",
+ });
+ }
+ },
+ CallExpression(node) {
+ const calleeStr = calleeToString(node.callee);
+ if (calleeStr.endsWith(".deserialize")) {
+ return;
+ }
+
+ for (const arg of node.arguments) {
+ if (isIdentifier(arg, "globalThis")) {
+ context.report({
+ node,
+ message:
+ "`globalThis` shouldn't be passed to function that can modify it. `globalThis` is the shared global inside the system module, and properties defined on it is visible from all modules.",
+ });
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-import-system-module-from-non-system.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-import-system-module-from-non-system.js
new file mode 100644
index 0000000000..7c964090be
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-import-system-module-from-non-system.js
@@ -0,0 +1,36 @@
+/**
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-import-system-module-from-non-system.html",
+ },
+ messages: {
+ rejectStaticImportSystemModuleFromNonSystem:
+ "System modules (*.sys.mjs) can be imported with static import declaration only from system modules.",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ ImportDeclaration(node) {
+ if (!node.source.value.endsWith(".sys.mjs")) {
+ return;
+ }
+
+ context.report({
+ node,
+ messageId: "rejectStaticImportSystemModuleFromNonSystem",
+ });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js
new file mode 100644
index 0000000000..c817de1fb4
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js
@@ -0,0 +1,95 @@
+/**
+ * @fileoverview Reject use of Cu.importGlobalProperties
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const path = require("path");
+
+const privilegedGlobals = Object.keys(
+ require("../environments/privileged.js").globals
+);
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-importGlobalProperties.html",
+ },
+ messages: {
+ unexpectedCall: "Unexpected call to Cu.importGlobalProperties",
+ unexpectedCallCuWebIdl:
+ "Unnecessary call to Cu.importGlobalProperties for {{name}} (webidl names are automatically imported)",
+ unexpectedCallXPCOMWebIdl:
+ "Unnecessary call to XPCOMUtils.defineLazyGlobalGetters for {{name}} (webidl names are automatically imported)",
+ },
+ schema: [
+ {
+ enum: ["everything", "allownonwebidl"],
+ },
+ ],
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (
+ node.callee.type !== "MemberExpression" ||
+ // TODO Bug 1501127: sjs files have their own sandbox, and do not inherit
+ // the Window backstage pass directly.
+ path.extname(context.getFilename()) == ".sjs"
+ ) {
+ return;
+ }
+ let memexp = node.callee;
+ if (
+ memexp.object.type === "Identifier" &&
+ // Only Cu, not Components.utils as `use-cc-etc` handles this for us.
+ memexp.object.name === "Cu" &&
+ memexp.property.type === "Identifier" &&
+ memexp.property.name === "importGlobalProperties"
+ ) {
+ if (context.options.includes("allownonwebidl")) {
+ for (let element of node.arguments[0].elements) {
+ if (privilegedGlobals.includes(element.value)) {
+ context.report({
+ node,
+ messageId: "unexpectedCallCuWebIdl",
+ data: { name: element.value },
+ });
+ }
+ }
+ } else {
+ context.report({ node, messageId: "unexpectedCall" });
+ }
+ }
+ if (
+ memexp.object.type === "Identifier" &&
+ memexp.object.name === "XPCOMUtils" &&
+ memexp.property.type === "Identifier" &&
+ memexp.property.name === "defineLazyGlobalGetters" &&
+ node.arguments.length >= 2
+ ) {
+ if (context.options.includes("allownonwebidl")) {
+ for (let element of node.arguments[1].elements) {
+ if (privilegedGlobals.includes(element.value)) {
+ context.report({
+ node,
+ messageId: "unexpectedCallXPCOMWebIdl",
+ data: { name: element.value },
+ });
+ }
+ }
+ } else {
+ context.report({ node, messageId: "unexpectedCall" });
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-lazy-imports-into-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-lazy-imports-into-globals.js
new file mode 100644
index 0000000000..5a9e0ab385
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-lazy-imports-into-globals.js
@@ -0,0 +1,75 @@
+/**
+ * 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/.
+ */
+
+"use strict";
+
+const helpers = require("../helpers");
+
+const callExpressionDefinitions = [
+ /^loader\.lazyGetter\((?:globalThis|window), "(\w+)"/,
+ /^loader\.lazyServiceGetter\((?:globalThis|window), "(\w+)"/,
+ /^loader\.lazyRequireGetter\((?:globalThis|window), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyGetter\((?:globalThis|window), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyModuleGetter\((?:globalThis|window), "(\w+)"/,
+ /^ChromeUtils\.defineModuleGetter\((?:globalThis|window), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyPreferenceGetter\((?:globalThis|window), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyProxy\((?:globalThis|window), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyScriptGetter\((?:globalThis|window), "(\w+)"/,
+ /^XPCOMUtils\.defineLazyServiceGetter\((?:globalThis|window), "(\w+)"/,
+ /^XPCOMUtils\.defineConstant\((?:globalThis|window), "(\w+)"/,
+ /^DevToolsUtils\.defineLazyModuleGetter\((?:globalThis|window), "(\w+)"/,
+ /^DevToolsUtils\.defineLazyGetter\((?:globalThis|window), "(\w+)"/,
+ /^Object\.defineProperty\((?:globalThis|window), "(\w+)"/,
+ /^Reflect\.defineProperty\((?:globalThis|window), "(\w+)"/,
+ /^this\.__defineGetter__\("(\w+)"/,
+];
+
+const callExpressionMultiDefinitions = [
+ "XPCOMUtils.defineLazyGlobalGetters(window,",
+ "XPCOMUtils.defineLazyGlobalGetters(globalThis,",
+ "XPCOMUtils.defineLazyModuleGetters(window,",
+ "XPCOMUtils.defineLazyModuleGetters(globalThis,",
+ "XPCOMUtils.defineLazyServiceGetters(window,",
+ "XPCOMUtils.defineLazyServiceGetters(globalThis,",
+ "ChromeUtils.defineESModuleGetters(window,",
+ "ChromeUtils.defineESModuleGetters(globalThis,",
+ "loader.lazyRequireGetter(window,",
+ "loader.lazyRequireGetter(globalThis,",
+];
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-lazy-imports-into-globals.html",
+ },
+ messages: {
+ rejectLazyImportsIntoGlobals:
+ "Non-system modules should not import into globalThis nor window. Prefer a lazy object holder",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let source;
+ try {
+ source = helpers.getASTSource(node);
+ } catch (e) {
+ return;
+ }
+
+ if (
+ callExpressionDefinitions.some(expr => source.match(expr)) ||
+ callExpressionMultiDefinitions.some(expr => source.startsWith(expr))
+ ) {
+ context.report({ node, messageId: "rejectLazyImportsIntoGlobals" });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-mixing-eager-and-lazy.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-mixing-eager-and-lazy.js
new file mode 100644
index 0000000000..0d158731ea
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-mixing-eager-and-lazy.js
@@ -0,0 +1,153 @@
+/**
+ * @fileoverview Reject use of lazy getters for modules that's loaded early in
+ * the startup process and not necessarily be lazy.
+ *
+ * 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/.
+ */
+
+"use strict";
+const helpers = require("../helpers");
+
+function isIdentifier(node, id) {
+ return node.type === "Identifier" && node.name === id;
+}
+
+function isString(node) {
+ return node.type === "Literal" && typeof node.value === "string";
+}
+
+function checkMixed(loadedModules, context, node, type, resourceURI) {
+ if (!loadedModules.has(resourceURI)) {
+ loadedModules.set(resourceURI, type);
+ }
+
+ if (loadedModules.get(resourceURI) === type) {
+ return;
+ }
+
+ context.report({
+ node,
+ messageId: "mixedEagerAndLazy",
+ data: { uri: resourceURI },
+ });
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-mixed-eager-and-lazy.html",
+ },
+ messages: {
+ mixedEagerAndLazy:
+ 'Module "{{uri}}" is loaded eagerly, and should not be used for lazy getter.',
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ const loadedModules = new Map();
+
+ return {
+ ImportDeclaration(node) {
+ const resourceURI = node.source.value;
+ checkMixed(loadedModules, context, node, "eager", resourceURI);
+ },
+ CallExpression(node) {
+ if (node.callee.type !== "MemberExpression") {
+ return;
+ }
+
+ let callerSource;
+ try {
+ callerSource = helpers.getASTSource(node.callee);
+ } catch (e) {
+ return;
+ }
+
+ if (
+ (callerSource === "ChromeUtils.import" ||
+ callerSource === "ChromeUtils.importESModule") &&
+ helpers.getIsTopLevelAndUnconditionallyExecuted(
+ context.getAncestors()
+ )
+ ) {
+ if (node.arguments.length < 1) {
+ return;
+ }
+ const resourceURINode = node.arguments[0];
+ if (!isString(resourceURINode)) {
+ return;
+ }
+ checkMixed(
+ loadedModules,
+ context,
+ node,
+ "eager",
+ resourceURINode.value
+ );
+ }
+
+ if (
+ callerSource === "XPCOMUtils.defineLazyModuleGetter" ||
+ callerSource === "ChromeUtils.defineModuleGetter"
+ ) {
+ if (node.arguments.length < 3) {
+ return;
+ }
+ if (!isIdentifier(node.arguments[0], "lazy")) {
+ return;
+ }
+
+ const resourceURINode = node.arguments[2];
+ if (!isString(resourceURINode)) {
+ return;
+ }
+ checkMixed(
+ loadedModules,
+ context,
+ node,
+ "lazy",
+ resourceURINode.value
+ );
+ } else if (
+ callerSource === "XPCOMUtils.defineLazyModuleGetters" ||
+ callerSource === "ChromeUtils.defineESModuleGetters"
+ ) {
+ if (node.arguments.length < 2) {
+ return;
+ }
+ if (!isIdentifier(node.arguments[0], "lazy")) {
+ return;
+ }
+
+ const obj = node.arguments[1];
+ if (obj.type !== "ObjectExpression") {
+ return;
+ }
+ for (let prop of obj.properties) {
+ if (prop.type !== "Property") {
+ continue;
+ }
+ if (prop.kind !== "init") {
+ continue;
+ }
+ const resourceURINode = prop.value;
+ if (!isString(resourceURINode)) {
+ continue;
+ }
+ checkMixed(
+ loadedModules,
+ context,
+ node,
+ "lazy",
+ resourceURINode.value
+ );
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-multiple-getters-calls.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-multiple-getters-calls.js
new file mode 100644
index 0000000000..56d09d3963
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-multiple-getters-calls.js
@@ -0,0 +1,81 @@
+/**
+ * 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/.
+ */
+
+"use strict";
+
+const helpers = require("../helpers");
+
+function findStatement(node) {
+ while (node && node.type !== "ExpressionStatement") {
+ node = node.parent;
+ }
+
+ return node;
+}
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-multiple-getters-calls.html",
+ },
+ messages: {
+ rejectMultipleCalls:
+ "ChromeUtils.defineESModuleGetters is already called for {{target}} in the same context. Please merge those calls",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ const parentToTargets = new Map();
+
+ return {
+ CallExpression(node) {
+ let callee = node.callee;
+ if (
+ callee.type === "MemberExpression" &&
+ isIdentifier(callee.object, "ChromeUtils") &&
+ isIdentifier(callee.property, "defineESModuleGetters")
+ ) {
+ const stmt = findStatement(node);
+ if (!stmt) {
+ return;
+ }
+
+ let target;
+ try {
+ target = helpers.getASTSource(node.arguments[0]);
+ } catch (e) {
+ return;
+ }
+
+ const parent = stmt.parent;
+ let targets;
+ if (parentToTargets.has(parent)) {
+ targets = parentToTargets.get(parent);
+ } else {
+ targets = new Set();
+ parentToTargets.set(parent, targets);
+ }
+
+ if (targets.has(target)) {
+ context.report({
+ node,
+ messageId: "rejectMultipleCalls",
+ data: { target },
+ });
+ }
+
+ targets.add(target);
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-osfile.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-osfile.js
new file mode 100644
index 0000000000..98fe94e26e
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-osfile.js
@@ -0,0 +1,51 @@
+/**
+ * @fileoverview Reject calls into OS.File. We're phasing this out in
+ * favour of IOUtils.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const { maybeGetMemberPropertyName } = require("../helpers");
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+function isOSProp(expr, prop) {
+ return (
+ maybeGetMemberPropertyName(expr.object) === "OS" &&
+ isIdentifier(expr.property, prop)
+ );
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-osfile.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ MemberExpression(node) {
+ if (isOSProp(node, "File")) {
+ context.report(
+ node,
+ "OS.File is deprecated. You should use IOUtils instead."
+ );
+ } else if (isOSProp(node, "Path")) {
+ context.report(
+ node,
+ "OS.Path is deprecated. You should use PathUtils instead."
+ );
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-relative-requires.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-relative-requires.js
new file mode 100644
index 0000000000..2f54dab019
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-relative-requires.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Reject some uses of require.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+
+const isRelativePath = function(path) {
+ return path.startsWith("./") || path.startsWith("../");
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-relative-requires.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ const path = helpers.getDevToolsRequirePath(node);
+ if (path && isRelativePath(path)) {
+ context.report(node, "relative paths are not allowed with require()");
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-scriptableunicodeconverter.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-scriptableunicodeconverter.js
new file mode 100644
index 0000000000..af3a49e3ef
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-scriptableunicodeconverter.js
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Reject calls into Ci.nsIScriptableUnicodeConverter. We're phasing this out in
+ * favour of TextEncoder or TextDecoder.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-scriptableunicodeconverter.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ MemberExpression(node) {
+ if (
+ isIdentifier(node.object, "Ci") &&
+ isIdentifier(node.property, "nsIScriptableUnicodeConverter")
+ ) {
+ context.report(
+ node,
+ "Ci.nsIScriptableUnicodeConverter is deprecated. You should use TextEncoder or TextDecoder instead."
+ );
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js
new file mode 100644
index 0000000000..80ec034196
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js
@@ -0,0 +1,42 @@
+/**
+ * @fileoverview Reject some uses of require.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-some-requires.html",
+ },
+ schema: [
+ {
+ type: "string",
+ },
+ ],
+ type: "problem",
+ },
+
+ create(context) {
+ if (typeof context.options[0] !== "string") {
+ throw new Error("reject-some-requires expects a regexp");
+ }
+ const RX = new RegExp(context.options[0]);
+
+ return {
+ CallExpression(node) {
+ const path = helpers.getDevToolsRequirePath(node);
+ if (path && RX.test(path)) {
+ context.report(node, `require(${path}) is not allowed`);
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-top-level-await.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-top-level-await.js
new file mode 100644
index 0000000000..b2cb458920
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-top-level-await.js
@@ -0,0 +1,45 @@
+/**
+ * @fileoverview Don't allow only() in tests
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-top-level-await.html",
+ },
+ messages: {
+ rejectTopLevelAwait:
+ "Top-level await is not currently supported in component files.",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ AwaitExpression(node) {
+ if (!helpers.getIsTopLevelScript(context.getAncestors())) {
+ return;
+ }
+ context.report({ node, messageId: "rejectTopLevelAwait" });
+ },
+ ForOfStatement(node) {
+ if (
+ !node.await ||
+ !helpers.getIsTopLevelScript(context.getAncestors())
+ ) {
+ return;
+ }
+ context.report({ node, messageId: "rejectTopLevelAwait" });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/rejects-requires-await.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/rejects-requires-await.js
new file mode 100644
index 0000000000..a7e68e55ae
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/rejects-requires-await.js
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Ensure Assert.rejects is preceded by await.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/reject-requires-await.html",
+ },
+ messages: {
+ rejectRequiresAwait: "Assert.rejects needs to be preceded by await.",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (node.callee.type === "MemberExpression") {
+ let memexp = node.callee;
+ if (
+ memexp.object.type === "Identifier" &&
+ memexp.object.name === "Assert" &&
+ memexp.property.type === "Identifier" &&
+ memexp.property.name === "rejects"
+ ) {
+ // We have ourselves an Assert.rejects.
+
+ if (node.parent.type !== "AwaitExpression") {
+ context.report({
+ node,
+ messageId: "rejectRequiresAwait",
+ });
+ }
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-cc-etc.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-cc-etc.js
new file mode 100644
index 0000000000..d86b70a7dc
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-cc-etc.js
@@ -0,0 +1,52 @@
+/**
+ * @fileoverview Reject use of Components.classes etc, prefer the shorthand instead.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const componentsMap = {
+ classes: "Cc",
+ interfaces: "Ci",
+ results: "Cr",
+ utils: "Cu",
+};
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-cc-etc.html",
+ },
+ type: "suggestion",
+ fixable: "code",
+ },
+
+ create(context) {
+ return {
+ MemberExpression(node) {
+ if (
+ node.object.type === "Identifier" &&
+ node.object.name === "Components" &&
+ node.property.type === "Identifier" &&
+ Object.getOwnPropertyNames(componentsMap).includes(node.property.name)
+ ) {
+ context.report({
+ node,
+ message: `Use ${
+ componentsMap[node.property.name]
+ } rather than Components.${node.property.name}`,
+ fix: fixer =>
+ fixer.replaceTextRange(
+ [node.range[0], node.range[1]],
+ componentsMap[node.property.name]
+ ),
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-generateqi.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-generateqi.js
new file mode 100644
index 0000000000..aef23a95f0
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-generateqi.js
@@ -0,0 +1,104 @@
+/**
+ * @fileoverview Reject use of XPCOMUtils.generateQI and JS-implemented
+ * QueryInterface methods in favor of ChromeUtils.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+function isMemberExpression(node, object, member) {
+ return (
+ node.type === "MemberExpression" &&
+ isIdentifier(node.object, object) &&
+ isIdentifier(node.property, member)
+ );
+}
+
+const MSG_NO_JS_QUERY_INTERFACE =
+ "Please use ChromeUtils.generateQI rather than manually creating " +
+ "JavaScript QueryInterface functions";
+
+const MSG_NO_XPCOMUTILS_GENERATEQI =
+ "Please use ChromeUtils.generateQI instead of XPCOMUtils.generateQI";
+
+function funcToGenerateQI(context, node) {
+ const sourceCode = context.getSourceCode();
+ const text = sourceCode.getText(node);
+
+ let interfaces = [];
+ let match;
+ let re = /\bCi\.([a-zA-Z0-9]+)\b|\b(nsI[A-Z][a-zA-Z0-9]+)\b/g;
+ while ((match = re.exec(text))) {
+ interfaces.push(match[1] || match[2]);
+ }
+
+ let ifaces = interfaces
+ .filter(iface => iface != "nsISupports")
+ .map(iface => JSON.stringify(iface))
+ .join(", ");
+
+ return `ChromeUtils.generateQI([${ifaces}])`;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-generateqi.html",
+ },
+ fixable: "code",
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ let { callee } = node;
+ if (isMemberExpression(callee, "XPCOMUtils", "generateQI")) {
+ context.report({
+ node,
+ message: MSG_NO_XPCOMUTILS_GENERATEQI,
+ fix(fixer) {
+ return fixer.replaceText(callee, "ChromeUtils.generateQI");
+ },
+ });
+ }
+ },
+
+ "AssignmentExpression > MemberExpression[property.name='QueryInterface']": function(
+ node
+ ) {
+ const { right } = node.parent;
+ if (right.type === "FunctionExpression") {
+ context.report({
+ node: node.parent,
+ message: MSG_NO_JS_QUERY_INTERFACE,
+ fix(fixer) {
+ return fixer.replaceText(right, funcToGenerateQI(context, right));
+ },
+ });
+ }
+ },
+
+ "Property[key.name='QueryInterface'][value.type='FunctionExpression']": function(
+ node
+ ) {
+ context.report({
+ node,
+ message: MSG_NO_JS_QUERY_INTERFACE,
+ fix(fixer) {
+ let generateQI = funcToGenerateQI(context, node.value);
+ return fixer.replaceText(node, `QueryInterface: ${generateQI}`);
+ },
+ });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-import.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-import.js
new file mode 100644
index 0000000000..835494e913
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-chromeutils-import.js
@@ -0,0 +1,78 @@
+/**
+ * @fileoverview Reject use of Cu.import and XPCOMUtils.defineLazyModuleGetter
+ * in favor of ChromeUtils.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+function isMemberExpression(node, object, member) {
+ return (
+ node.type === "MemberExpression" &&
+ isIdentifier(node.object, object) &&
+ isIdentifier(node.property, member)
+ );
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-chromeutils-import.html",
+ },
+ fixable: "code",
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (node.callee.type !== "MemberExpression") {
+ return;
+ }
+
+ let { callee } = node;
+
+ // Is the expression starting with `Cu` or `Components.utils`?
+ if (
+ (isIdentifier(callee.object, "Cu") ||
+ isMemberExpression(callee.object, "Components", "utils")) &&
+ isIdentifier(callee.property, "import")
+ ) {
+ context.report({
+ node,
+ message: "Please use ChromeUtils.import instead of Cu.import",
+ fix(fixer) {
+ return fixer.replaceText(callee, "ChromeUtils.import");
+ },
+ });
+ }
+
+ if (
+ isMemberExpression(callee, "XPCOMUtils", "defineLazyModuleGetter") &&
+ node.arguments.length < 4
+ ) {
+ context.report({
+ node,
+ message:
+ "Please use ChromeUtils.defineModuleGetter instead of " +
+ "XPCOMUtils.defineLazyModuleGetter",
+ fix(fixer) {
+ return fixer.replaceText(
+ callee,
+ "ChromeUtils.defineModuleGetter"
+ );
+ },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-default-preference-values.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-default-preference-values.js
new file mode 100644
index 0000000000..3c621bed1b
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-default-preference-values.js
@@ -0,0 +1,50 @@
+/**
+ * @fileoverview Require providing a second parameter to get*Pref
+ * methods instead of using a try/catch block.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-default-preference-values.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ TryStatement(node) {
+ let types = ["Bool", "Char", "Float", "Int"];
+ let methods = types.map(type => "get" + type + "Pref");
+ if (
+ node.block.type != "BlockStatement" ||
+ node.block.body.length != 1
+ ) {
+ return;
+ }
+
+ let firstStm = node.block.body[0];
+ if (
+ firstStm.type != "ExpressionStatement" ||
+ firstStm.expression.type != "AssignmentExpression" ||
+ firstStm.expression.right.type != "CallExpression" ||
+ firstStm.expression.right.callee.type != "MemberExpression" ||
+ firstStm.expression.right.callee.property.type != "Identifier" ||
+ !methods.includes(firstStm.expression.right.callee.property.name)
+ ) {
+ return;
+ }
+
+ let msg = "provide a default value instead of using a try/catch block";
+ context.report(node, msg);
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-includes-instead-of-indexOf.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-includes-instead-of-indexOf.js
new file mode 100644
index 0000000000..b30977a9b6
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-includes-instead-of-indexOf.js
@@ -0,0 +1,47 @@
+/**
+ * @fileoverview Use .includes instead of .indexOf
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-includes-instead-of-indexOf.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ BinaryExpression(node) {
+ if (
+ node.left.type != "CallExpression" ||
+ node.left.callee.type != "MemberExpression" ||
+ node.left.callee.property.type != "Identifier" ||
+ node.left.callee.property.name != "indexOf"
+ ) {
+ return;
+ }
+
+ if (
+ (["!=", "!==", "==", "==="].includes(node.operator) &&
+ node.right.type == "UnaryExpression" &&
+ node.right.operator == "-" &&
+ node.right.argument.type == "Literal" &&
+ node.right.argument.value == 1) ||
+ ([">=", "<"].includes(node.operator) &&
+ node.right.type == "Literal" &&
+ node.right.value == 0)
+ ) {
+ context.report(node, "use .includes instead of .indexOf");
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-isInstance.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-isInstance.js
new file mode 100644
index 0000000000..427870259a
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-isInstance.js
@@ -0,0 +1,155 @@
+/**
+ * @fileoverview Reject use of instanceof against DOM interfaces
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const fs = require("fs");
+
+const { maybeGetMemberPropertyName } = require("../helpers");
+
+const privilegedGlobals = Object.keys(
+ require("../environments/privileged.js").globals
+);
+
+// -----------------------------------------------------------------------------
+// Rule Definition
+// -----------------------------------------------------------------------------
+
+/**
+ * Whether an identifier is defined by eslint configuration.
+ * `env: { browser: true }` or `globals: []` for example.
+ * @param {import("eslint-scope").Scope} currentScope
+ * @param {import("estree").Identifier} id
+ */
+function refersToEnvironmentGlobals(currentScope, id) {
+ const reference = currentScope.references.find(ref => ref.identifier === id);
+ const { resolved } = reference || {};
+ if (!resolved) {
+ return false;
+ }
+
+ // No definition in script files; defined via .eslintrc
+ return resolved.scope.type === "global" && resolved.defs.length === 0;
+}
+
+/**
+ * Whether a node points to a DOM interface.
+ * Includes direct references to interfaces objects and also indirect references
+ * via property access.
+ * OS.File and lazy.(Foo) are explicitly excluded.
+ *
+ * @example HTMLElement
+ * @example win.HTMLElement
+ * @example iframe.contentWindow.HTMLElement
+ * @example foo.HTMLElement
+ *
+ * @param {import("eslint-scope").Scope} currentScope
+ * @param {import("estree").Node} node
+ */
+function pointsToDOMInterface(currentScope, node) {
+ if (node.type === "MemberExpression") {
+ const objectName = maybeGetMemberPropertyName(node.object);
+ if (objectName === "lazy") {
+ // lazy.Foo is probably a non-IDL import.
+ return false;
+ }
+ if (objectName === "OS" && node.property.name === "File") {
+ // OS.File is an exception that is not a Web IDL interface
+ return false;
+ }
+ // For `win.Foo`, `iframe.contentWindow.Foo`, or such.
+ return privilegedGlobals.includes(node.property.name);
+ }
+
+ if (
+ node.type === "Identifier" &&
+ refersToEnvironmentGlobals(currentScope, node)
+ ) {
+ return privilegedGlobals.includes(node.name);
+ }
+
+ return false;
+}
+
+/**
+ * @param {import("eslint").Rule.RuleContext} context
+ */
+function isChromeContext(context) {
+ const filename = context.getFilename();
+ const isChromeFileName =
+ filename.endsWith(".sys.mjs") ||
+ filename.endsWith(".jsm") ||
+ filename.endsWith(".jsm.js");
+ if (isChromeFileName) {
+ return true;
+ }
+
+ if (filename.endsWith(".xhtml")) {
+ // Treat scripts in XUL files as chrome scripts
+ // Note: readFile is needed as getSourceCode() only gives JS blocks
+ return fs.readFileSync(filename).includes("there.is.only.xul");
+ }
+
+ // Treat scripts as chrome privileged when using:
+ // 1. ChromeUtils, but not SpecialPowers.ChromeUtils
+ // 2. BrowserTestUtils, PlacesUtils
+ // 3. document.createXULElement
+ // 4. loader.lazyRequireGetter
+ // 5. Services.foo, but not SpecialPowers.Services.foo
+ // 6. evalInSandbox
+ const source = context.getSourceCode().text;
+ return !!source.match(
+ /(^|\s)ChromeUtils|BrowserTestUtils|PlacesUtils|createXULElement|lazyRequireGetter|(^|\s)Services\.|evalInSandbox/
+ );
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-isInstance.html",
+ },
+ fixable: "code",
+ schema: [],
+ type: "problem",
+ },
+ /**
+ * @param {import("eslint").Rule.RuleContext} context
+ */
+ create(context) {
+ if (!isChromeContext(context)) {
+ return {};
+ }
+
+ return {
+ BinaryExpression(node) {
+ const { operator, right } = node;
+ if (
+ operator === "instanceof" &&
+ pointsToDOMInterface(context.getScope(), right)
+ ) {
+ context.report({
+ node,
+ message:
+ "Please prefer .isInstance() in chrome scripts over the standard instanceof operator for DOM interfaces, " +
+ "since the latter will return false when the object is created from a different context.",
+ fix(fixer) {
+ const sourceCode = context.getSourceCode();
+ return fixer.replaceText(
+ node,
+ `${sourceCode.getText(right)}.isInstance(${sourceCode.getText(
+ node.left
+ )})`
+ );
+ },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-ownerGlobal.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-ownerGlobal.js
new file mode 100644
index 0000000000..eb0391ef5f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-ownerGlobal.js
@@ -0,0 +1,40 @@
+/**
+ * @fileoverview Require .ownerGlobal instead of .ownerDocument.defaultView.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-ownerGlobal.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ MemberExpression(node) {
+ if (
+ node.property.type != "Identifier" ||
+ node.property.name != "defaultView" ||
+ node.object.type != "MemberExpression" ||
+ node.object.property.type != "Identifier" ||
+ node.object.property.name != "ownerDocument"
+ ) {
+ return;
+ }
+
+ context.report(
+ node,
+ "use .ownerGlobal instead of .ownerDocument.defaultView"
+ );
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-returnValue.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-returnValue.js
new file mode 100644
index 0000000000..db889cf3b9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-returnValue.js
@@ -0,0 +1,41 @@
+/**
+ * @fileoverview Warn when idempotent methods are called and their return value is unused.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-returnValue.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ ExpressionStatement(node) {
+ if (
+ node.expression?.type != "CallExpression" ||
+ node.expression.callee?.type != "MemberExpression" ||
+ node.expression.callee.property?.type != "Identifier" ||
+ !["concat", "join", "slice"].includes(
+ node.expression.callee.property?.name
+ )
+ ) {
+ return;
+ }
+
+ context.report(
+ node,
+ `{Array/String}.${node.expression.callee.property.name} doesn't modify the instance in-place`
+ );
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-services.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-services.js
new file mode 100644
index 0000000000..a4ed619aa3
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-services.js
@@ -0,0 +1,104 @@
+/**
+ * @fileoverview Require use of Services.* rather than getService.
+ *
+ * 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/.
+ */
+
+"use strict";
+const helpers = require("../helpers");
+
+let servicesInterfaceMap = helpers.servicesData;
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-services.html",
+ },
+ // fixable: "code",
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ CallExpression(node) {
+ if (!node.callee || !node.callee.property) {
+ return;
+ }
+
+ if (
+ node.callee.property.type == "Identifier" &&
+ node.callee.property.name == "defineLazyServiceGetter" &&
+ node.arguments.length == 4 &&
+ node.arguments[3].type == "Literal" &&
+ node.arguments[3].value in servicesInterfaceMap
+ ) {
+ let serviceName = servicesInterfaceMap[node.arguments[3].value];
+
+ context.report(
+ node,
+ `Use Services.${serviceName} rather than defineLazyServiceGetter.`
+ );
+ return;
+ }
+
+ if (
+ node.callee.property.type == "Identifier" &&
+ node.callee.property.name == "defineLazyServiceGetters" &&
+ node.arguments.length == 2 &&
+ node.arguments[1].type == "ObjectExpression"
+ ) {
+ for (let property of node.arguments[1].properties) {
+ if (
+ property.value.type == "ArrayExpression" &&
+ property.value.elements.length == 2 &&
+ property.value.elements[1].value in servicesInterfaceMap
+ ) {
+ let serviceName =
+ servicesInterfaceMap[property.value.elements[1].value];
+
+ context.report(
+ property.value,
+ `Use Services.${serviceName} rather than defineLazyServiceGetters.`
+ );
+ }
+ }
+ return;
+ }
+
+ if (
+ node.callee.property.type != "Identifier" ||
+ node.callee.property.name != "getService" ||
+ node.arguments.length != 1 ||
+ !node.arguments[0].property ||
+ node.arguments[0].property.type != "Identifier" ||
+ !node.arguments[0].property.name ||
+ !(node.arguments[0].property.name in servicesInterfaceMap)
+ ) {
+ return;
+ }
+
+ let serviceName = servicesInterfaceMap[node.arguments[0].property.name];
+ context.report({
+ node,
+ message: `Use Services.${serviceName} rather than getService().`,
+ // This is not enabled by default as for mochitest plain tests we
+ // would need to replace with `SpecialPowers.Services.${serviceName}`.
+ // At the moment we do not have an easy way to detect that.
+ // fix(fixer) {
+ // let sourceCode = context.getSourceCode();
+ // return fixer.replaceTextRange(
+ // [
+ // sourceCode.getFirstToken(node.callee).range[0],
+ // sourceCode.getLastToken(node).range[1],
+ // ],
+ // `Services.${serviceName}`
+ // );
+ // },
+ });
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-static-import.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-static-import.js
new file mode 100644
index 0000000000..a68bf5297f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/use-static-import.js
@@ -0,0 +1,87 @@
+/**
+ * @fileoverview Require use of static imports where possible.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const helpers = require("../helpers");
+
+function isIdentifier(node, id) {
+ return node && node.type === "Identifier" && node.name === id;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/use-static-import.html",
+ },
+ fixable: "code",
+ messages: {
+ useStaticImport:
+ "Please use static import instead of ChromeUtils.importESModule",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ VariableDeclarator(node) {
+ if (
+ node.init?.type != "CallExpression" ||
+ node.init?.callee?.type != "MemberExpression" ||
+ !context.getFilename().endsWith(".sys.mjs") ||
+ !helpers.isTopLevel(context.getAncestors())
+ ) {
+ return;
+ }
+
+ let callee = node.init.callee;
+
+ if (
+ isIdentifier(callee.object, "ChromeUtils") &&
+ isIdentifier(callee.property, "importESModule") &&
+ callee.parent.arguments.length == 1
+ ) {
+ let sourceCode = context.getSourceCode();
+ let importItemSource;
+ if (node.id.type != "ObjectPattern") {
+ importItemSource = sourceCode.getText(node.id);
+ } else {
+ importItemSource = "{ ";
+ let initial = true;
+ for (let property of node.id.properties) {
+ if (!initial) {
+ importItemSource += ", ";
+ }
+ initial = false;
+ if (property.key.name == property.value.name) {
+ importItemSource += property.key.name;
+ } else {
+ importItemSource += `${property.key.name} as ${property.value.name}`;
+ }
+ }
+ importItemSource += " }";
+ }
+
+ context.report({
+ node: node.parent,
+ messageId: "useStaticImport",
+ fix(fixer) {
+ return fixer.replaceText(
+ node.parent,
+ `import ${importItemSource} from ${sourceCode.getText(
+ callee.parent.arguments[0]
+ )}`
+ );
+ },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-ci-uses.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-ci-uses.js
new file mode 100644
index 0000000000..4aa832f9a5
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-ci-uses.js
@@ -0,0 +1,167 @@
+/**
+ * @fileoverview Reject uses of unknown interfaces on Ci and properties of those
+ * interfaces.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const os = require("os");
+const helpers = require("../helpers");
+
+// These interfaces are all platform specific, so may be not present
+// on all platforms.
+const platformSpecificInterfaces = new Map([
+ ["nsIAboutThirdParty", "windows"],
+ ["nsIAboutWindowsMessages", "windows"],
+ ["nsIJumpListItem", "windows"],
+ ["nsIJumpListLink", "windows"],
+ ["nsIJumpListSeparator", "windows"],
+ ["nsIJumpListShortcut", "windows"],
+ ["nsITaskbarWindowPreview", "windows"],
+ ["nsIWindowsAlertsService", "windows"],
+ ["nsIWinAppHelper", "windows"],
+ ["nsIWinTaskbar", "windows"],
+ ["nsIWinTaskSchedulerService", "windows"],
+ ["nsIWindowsRegKey", "windows"],
+ ["nsIWindowsPackageManager", "windows"],
+ ["nsIWindowsShellService", "windows"],
+ ["nsIAccessibleMacEvent", "darwin"],
+ ["nsIAccessibleMacInterface", "darwin"],
+ ["nsILocalFileMac", "darwin"],
+ ["nsIAccessibleMacEvent", "darwin"],
+ ["nsIMacAttributionService", "darwin"],
+ ["nsIMacShellService", "darwin"],
+ ["nsIMacDockSupport", "darwin"],
+ ["nsIMacFinderProgress", "darwin"],
+ ["nsIMacPreferencesReader", "darwin"],
+ ["nsIMacSharingService", "darwin"],
+ ["nsIMacUserActivityUpdater", "darwin"],
+ ["nsIMacWebAppUtils", "darwin"],
+ ["nsIStandaloneNativeMenu", "darwin"],
+ ["nsITouchBarHelper", "darwin"],
+ ["nsITouchBarInput", "darwin"],
+ ["nsITouchBarUpdater", "darwin"],
+ ["mozISandboxReporter", "linux"],
+ ["nsIApplicationChooser", "linux"],
+ ["nsIGNOMEShellService", "linux"],
+ ["nsIGtkTaskbarProgress", "linux"],
+
+ // These are used in the ESLint test code.
+ ["amIFoo", "any"],
+ ["nsIMeh", "any"],
+ // Can't easily detect android builds from ESLint at the moment.
+ ["nsIAndroidBridge", "any"],
+ ["nsIAndroidView", "any"],
+ // Code coverage is enabled only for certain builds (MOZ_CODE_COVERAGE).
+ ["nsICodeCoverage", "any"],
+ // Layout debugging is enabled only for certain builds (MOZ_LAYOUT_DEBUGGER).
+ ["nsILayoutDebuggingTools", "any"],
+ // Sandbox test is only enabled for certain configurations (MOZ_SANDBOX,
+ // MOZ_DEBUG, ENABLE_TESTS).
+ ["mozISandboxTest", "any"],
+]);
+
+function interfaceHasProperty(interfaceName, propertyName) {
+ // `Ci.nsIFoo.number` is valid, it returns the iid.
+ if (propertyName == "number") {
+ return true;
+ }
+
+ let interfaceInfo = helpers.xpidlData.get(interfaceName);
+
+ if (!interfaceInfo) {
+ return true;
+ }
+
+ // If the property is not in the lists of consts for this interface, check
+ // any parents as well.
+ if (!interfaceInfo.consts.find(e => e.name === propertyName)) {
+ if (interfaceInfo.parent && interfaceInfo.parent != "nsISupports") {
+ return interfaceHasProperty(interfaceName.parent, propertyName);
+ }
+ return false;
+ }
+ return true;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/valid-ci-uses.html",
+ },
+ messages: {
+ missingInterface:
+ "{{ interface }} is defined in this rule's platform specific list, but is not available",
+ unknownInterface: "Use of unknown interface Ci.{{ interface}}",
+ unknownProperty:
+ "Use of unknown property Ci.{{ interface }}.{{ property }}",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ return {
+ MemberExpression(node) {
+ if (
+ node.computed === false &&
+ node.type === "MemberExpression" &&
+ node.object.type === "Identifier" &&
+ node.object.name === "Ci" &&
+ node.property.type === "Identifier" &&
+ node.property.name.includes("I")
+ ) {
+ if (!helpers.xpidlData.get(node.property.name)) {
+ let platformSpecific = platformSpecificInterfaces.get(
+ node.property.name
+ );
+ if (!platformSpecific) {
+ context.report({
+ node,
+ messageId: "unknownInterface",
+ data: {
+ interface: node.property.name,
+ },
+ });
+ } else if (platformSpecific == os.platform) {
+ context.report({
+ node,
+ messageId: "missingInterface",
+ data: {
+ interface: node.property.name,
+ },
+ });
+ }
+ }
+ }
+
+ if (
+ node.computed === false &&
+ node.object.type === "MemberExpression" &&
+ node.object.object.type === "Identifier" &&
+ node.object.object.name === "Ci" &&
+ node.object.property.type === "Identifier" &&
+ node.object.property.name.includes("I") &&
+ node.property.type === "Identifier"
+ ) {
+ if (
+ !interfaceHasProperty(node.object.property.name, node.property.name)
+ ) {
+ context.report({
+ node,
+ messageId: "unknownProperty",
+ data: {
+ interface: node.object.property.name,
+ property: node.property.name,
+ },
+ });
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js
new file mode 100644
index 0000000000..f5d5f15cc8
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-lazy.js
@@ -0,0 +1,220 @@
+/**
+ * @fileoverview Ensures that definitions and uses of properties on the
+ * ``lazy`` object are valid.
+ *
+ * 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/.
+ */
+
+"use strict";
+const helpers = require("../helpers");
+
+const items = [
+ "loader",
+ "XPCOMUtils",
+ "Integration",
+ "ChromeUtils",
+ "DevToolsUtils",
+ "Object",
+ "Reflect",
+];
+
+const callExpressionDefinitions = [
+ /^loader\.lazyGetter\(lazy, "(\w+)"/,
+ /^loader\.lazyServiceGetter\(lazy, "(\w+)"/,
+ /^loader\.lazyRequireGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyGetter\(lazy, "(\w+)"/,
+ /^Integration\.downloads\.defineESModuleGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyModuleGetter\(lazy, "(\w+)"/,
+ /^ChromeUtils\.defineModuleGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyPreferenceGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyProxy\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyScriptGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineLazyServiceGetter\(lazy, "(\w+)"/,
+ /^XPCOMUtils\.defineConstant\(lazy, "(\w+)"/,
+ /^DevToolsUtils\.defineLazyModuleGetter\(lazy, "(\w+)"/,
+ /^DevToolsUtils\.defineLazyGetter\(lazy, "(\w+)"/,
+ /^Object\.defineProperty\(lazy, "(\w+)"/,
+ /^Reflect\.defineProperty\(lazy, "(\w+)"/,
+];
+
+const callExpressionMultiDefinitions = [
+ "ChromeUtils.defineESModuleGetters(lazy,",
+ "XPCOMUtils.defineLazyModuleGetters(lazy,",
+ "XPCOMUtils.defineLazyServiceGetters(lazy,",
+ "Object.defineProperties(lazy,",
+ "loader.lazyRequireGetter(lazy,",
+];
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/valid-lazy.html",
+ },
+ messages: {
+ duplicateSymbol: "Duplicate symbol {{name}} being added to lazy.",
+ incorrectType: "Unexpected literal for property name {{name}}",
+ unknownProperty: "Unknown lazy member property {{name}}",
+ unusedProperty: "Unused lazy property {{name}}",
+ topLevelAndUnconditional:
+ "Lazy property {{name}} is used at top-level unconditionally. It should be non-lazy.",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ let lazyProperties = new Map();
+ let unknownProperties = [];
+
+ function addProp(node, name) {
+ if (lazyProperties.has(name)) {
+ context.report({
+ node,
+ messageId: "duplicateSymbol",
+ data: { name },
+ });
+ return;
+ }
+ lazyProperties.set(name, { used: false, node });
+ }
+
+ function setPropertiesFromArgument(node, arg) {
+ if (arg.type === "ObjectExpression") {
+ for (let prop of arg.properties) {
+ if (prop.key.type == "Literal") {
+ context.report({
+ node,
+ messageId: "incorrectType",
+ data: { name: prop.key.value },
+ });
+ continue;
+ }
+ addProp(node, prop.key.name);
+ }
+ } else if (arg.type === "ArrayExpression") {
+ for (let prop of arg.elements) {
+ if (prop.type != "Literal") {
+ continue;
+ }
+ addProp(node, prop.value);
+ }
+ }
+ }
+
+ return {
+ VariableDeclarator(node) {
+ if (
+ node.id.type === "Identifier" &&
+ node.id.name == "lazy" &&
+ node.init.type == "CallExpression" &&
+ node.init.callee.name == "createLazyLoaders"
+ ) {
+ setPropertiesFromArgument(node, node.init.arguments[0]);
+ }
+ },
+
+ CallExpression(node) {
+ if (
+ node.callee.type != "MemberExpression" ||
+ (node.callee.object.type == "MemberExpression" &&
+ !items.includes(node.callee.object.object.name)) ||
+ (node.callee.object.type != "MemberExpression" &&
+ !items.includes(node.callee.object.name))
+ ) {
+ return;
+ }
+
+ let source;
+ try {
+ source = helpers.getASTSource(node);
+ } catch (e) {
+ return;
+ }
+
+ for (let reg of callExpressionDefinitions) {
+ let match = source.match(reg);
+ if (match) {
+ if (lazyProperties.has(match[1])) {
+ context.report({
+ node,
+ messageId: "duplicateSymbol",
+ data: { name: match[1] },
+ });
+ return;
+ }
+ lazyProperties.set(match[1], { used: false, node });
+ break;
+ }
+ }
+
+ if (
+ callExpressionMultiDefinitions.some(expr =>
+ source.startsWith(expr)
+ ) &&
+ node.arguments[1]
+ ) {
+ setPropertiesFromArgument(node, node.arguments[1]);
+ }
+ },
+
+ MemberExpression(node) {
+ if (node.computed || node.object.type !== "Identifier") {
+ return;
+ }
+
+ let name;
+ if (node.object.name == "lazy") {
+ name = node.property.name;
+ } else {
+ return;
+ }
+ let property = lazyProperties.get(name);
+ if (!property) {
+ // These will be reported on Program:exit - some definitions may
+ // be after first use, so we need to wait until we've processed
+ // the whole file before reporting.
+ unknownProperties.push({ name, node });
+ } else {
+ property.used = true;
+ }
+ if (
+ helpers.getIsTopLevelAndUnconditionallyExecuted(
+ context.getAncestors()
+ )
+ ) {
+ context.report({
+ node,
+ messageId: "topLevelAndUnconditional",
+ data: { name },
+ });
+ }
+ },
+
+ "Program:exit": function() {
+ for (let { name, node } of unknownProperties) {
+ let property = lazyProperties.get(name);
+ if (!property) {
+ context.report({
+ node,
+ messageId: "unknownProperty",
+ data: { name },
+ });
+ } else {
+ property.used = true;
+ }
+ }
+ for (let [name, property] of lazyProperties.entries()) {
+ if (!property.used) {
+ context.report({
+ node: property.node,
+ messageId: "unusedProperty",
+ data: { name },
+ });
+ }
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services-property.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services-property.js
new file mode 100644
index 0000000000..c3600fcb25
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services-property.js
@@ -0,0 +1,126 @@
+/**
+ * @fileoverview Ensures that property accesses on Services.<alias> are valid.
+ * Although this largely duplicates the valid-services rule, the checks here
+ * require an objdir and a manual run.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const helpers = require("../helpers");
+
+function findInterfaceNames(name) {
+ let interfaces = [];
+ for (let [key, value] of Object.entries(helpers.servicesData)) {
+ if (value == name) {
+ interfaces.push(key);
+ }
+ }
+ return interfaces;
+}
+
+function isInInterface(interfaceName, name) {
+ let interfaceDetails = helpers.xpidlData.get(interfaceName);
+
+ // TODO: Bug 1790261 - check only methods if the expression is callable.
+ if (interfaceDetails.methods.some(m => m.name == name)) {
+ return true;
+ }
+
+ if (interfaceDetails.consts.some(c => c.name == name)) {
+ return true;
+ }
+
+ if (interfaceDetails.parent) {
+ return isInInterface(interfaceDetails.parent, name);
+ }
+ return false;
+}
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/valid-services-property.html",
+ },
+ messages: {
+ unknownProperty:
+ "Unknown property access Services.{{ alias }}.{{ propertyName }}, Interfaces: {{ checkedInterfaces }}",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ let servicesInterfaceMap = helpers.servicesData;
+ let serviceAliases = new Set([
+ ...Object.values(servicesInterfaceMap),
+ // This is defined only for Android, so most builds won't pick it up.
+ "androidBridge",
+ // These are defined without interfaces and hence are not in the services map.
+ "cpmm",
+ "crashmanager",
+ "mm",
+ "ppmm",
+ // The new xulStore also does not have an interface.
+ "xulStore",
+ ]);
+ return {
+ MemberExpression(node) {
+ if (node.computed || node.object.type !== "Identifier") {
+ return;
+ }
+
+ let mainNode;
+ if (node.object.name == "Services") {
+ mainNode = node;
+ } else if (
+ node.property.name == "Services" &&
+ node.parent.type == "MemberExpression"
+ ) {
+ mainNode = node.parent;
+ } else {
+ return;
+ }
+
+ let alias = mainNode.property.name;
+ if (!serviceAliases.has(alias)) {
+ return;
+ }
+
+ if (
+ mainNode.parent.type == "MemberExpression" &&
+ !mainNode.parent.computed
+ ) {
+ let propertyName = mainNode.parent.property.name;
+ if (propertyName == "wrappedJSObject") {
+ return;
+ }
+ let interfaces = findInterfaceNames(alias);
+ if (!interfaces.length) {
+ return;
+ }
+
+ let checkedInterfaces = [];
+ for (let item of interfaces) {
+ if (isInInterface(item, propertyName)) {
+ return;
+ }
+ checkedInterfaces.push(item);
+ }
+ context.report({
+ node,
+ messageId: "unknownProperty",
+ data: {
+ alias,
+ propertyName,
+ checkedInterfaces,
+ },
+ });
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services.js
new file mode 100644
index 0000000000..4549ebd5fe
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/valid-services.js
@@ -0,0 +1,59 @@
+/**
+ * @fileoverview Ensures that Services uses have valid property names.
+ *
+ * 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/.
+ */
+
+"use strict";
+const helpers = require("../helpers");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/valid-services.html",
+ },
+ type: "problem",
+ },
+
+ create(context) {
+ let servicesInterfaceMap = helpers.servicesData;
+ let serviceAliases = new Set([
+ ...Object.values(servicesInterfaceMap),
+ // This is defined only for Android, so most builds won't pick it up.
+ "androidBridge",
+ // These are defined without interfaces and hence are not in the services map.
+ "cpmm",
+ "crashmanager",
+ "mm",
+ "ppmm",
+ // The new xulStore also does not have an interface.
+ "xulStore",
+ ]);
+ return {
+ MemberExpression(node) {
+ if (node.computed || node.object.type !== "Identifier") {
+ return;
+ }
+
+ let alias;
+ if (node.object.name == "Services") {
+ alias = node.property.name;
+ } else if (
+ node.property.name == "Services" &&
+ node.parent.type == "MemberExpression"
+ ) {
+ alias = node.parent.property.name;
+ } else {
+ return;
+ }
+
+ if (!serviceAliases.has(alias)) {
+ context.report(node, `Unknown Services member property ${alias}`);
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
new file mode 100644
index 0000000000..954fca74ee
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js
@@ -0,0 +1,36 @@
+/**
+ * @fileoverview Marks all var declarations that are not at the top level
+ * invalid.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+var helpers = require("../helpers");
+
+module.exports = {
+ meta: {
+ docs: {
+ url:
+ "https://firefox-source-docs.mozilla.org/code-quality/lint/linters/eslint-plugin-mozilla/var-only-at-top-level.html",
+ },
+ type: "suggestion",
+ },
+
+ create(context) {
+ return {
+ VariableDeclaration(node) {
+ if (node.kind === "var") {
+ if (helpers.getIsTopLevelScript(context.getAncestors())) {
+ return;
+ }
+
+ context.report(node, "Unexpected var, use let or const instead.");
+ }
+ },
+ };
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/services.json b/tools/lint/eslint/eslint-plugin-mozilla/lib/services.json
new file mode 100644
index 0000000000..65c1fbcb6c
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/services.json
@@ -0,0 +1,61 @@
+{
+ "mozIJSSubScriptLoader": "scriptloader",
+ "mozILocaleService": "locale",
+ "mozIMozIntl": "intl",
+ "mozIStorageService": "storage",
+ "nsIAppShellService": "appShell",
+ "nsIAppStartup": "startup",
+ "nsIBlocklistService": "blocklist",
+ "nsICacheStorageService": "cache2",
+ "nsICategoryManager": "catMan",
+ "nsIClearDataService": "clearData",
+ "nsIClipboard": "clipboard",
+ "nsIConsoleService": "console",
+ "nsICookieBannerService": "cookieBanners",
+ "nsICookieManager": "cookies",
+ "nsICookieService": "cookies",
+ "nsICrashReporter": "appinfo",
+ "nsIDAPTelemetry": "DAPTelemetry",
+ "nsIDOMRequestService": "DOMRequest",
+ "nsIDOMStorageManager": "domStorageManager",
+ "nsIDNSService": "dns",
+ "nsIDirectoryService": "dirsvc",
+ "nsIDroppedLinkHandler": "droppedLinkHandler",
+ "nsIEffectiveTLDService": "eTLD",
+ "nsIEnterprisePolicies": "policies",
+ "nsIEnvironment": "env",
+ "nsIEventListenerService": "els",
+ "nsIFOG": "fog",
+ "nsIFocusManager": "focus",
+ "nsIIOService": "io",
+ "nsILoadContextInfoFactory": "loadContextInfo",
+ "nsILocalStorageManager": "domStorageManager",
+ "nsILoginManager": "logins",
+ "nsINetUtil": "io",
+ "nsIObserverService": "obs",
+ "nsIPermissionManager": "perms",
+ "nsIPrefBranch": "prefs",
+ "nsIPrefService": "prefs",
+ "nsIProfiler": "profiler",
+ "nsIPromptService": "prompt",
+ "nsIProperties": "dirsvc",
+ "nsIPropertyBag2": "sysinfo",
+ "nsIQuotaManagerService": "qms",
+ "nsIScriptSecurityManager": "scriptSecurityManager",
+ "nsISearchService": "search",
+ "nsISessionStorageService": "sessionStorage",
+ "nsISpeculativeConnect": "io",
+ "nsIStringBundleService": "strings",
+ "nsISystemInfo": "sysinfo",
+ "nsITelemetry": "telemetry",
+ "nsITextToSubURI": "textToSubURI",
+ "nsIThreadManager": "tm",
+ "nsIURIFixup": "uriFixup",
+ "nsIURLFormatter": "urlFormatter",
+ "nsIUUIDGenerator": "uuid",
+ "nsIVersionComparator": "vc",
+ "nsIWindowMediator": "wm",
+ "nsIWindowWatcher": "ww",
+ "nsIXULAppInfo": "appinfo",
+ "nsIXULRuntime": "appinfo"
+} \ No newline at end of file
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt b/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
new file mode 100644
index 0000000000..f436a5f50b
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/manifest.tt
@@ -0,0 +1,10 @@
+[
+ {
+ "filename": "eslint-plugin-mozilla.tar.gz",
+ "size": 11340023,
+ "algorithm": "sha512",
+ "digest": "f98c6d1d1065cd6ea7ec8878be5eefef0510f02f6bb389b70c29b753b54067e92a0ec2e3e9f53b3a107e3e2facfd3a698caf79310dcb452bbf03b22a35472a4f",
+ "unpack": true,
+ "visibility": "public"
+ }
+] \ No newline at end of file
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/package-lock.json b/tools/lint/eslint/eslint-plugin-mozilla/package-lock.json
new file mode 100644
index 0000000000..3f153ccec5
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package-lock.json
@@ -0,0 +1,6733 @@
+{
+ "name": "eslint-plugin-mozilla",
+ "version": "3.0.1",
+ "lockfileVersion": 2,
+ "requires": true,
+ "packages": {
+ "": {
+ "name": "eslint-plugin-mozilla",
+ "version": "3.0.1",
+ "license": "MPL-2.0",
+ "dependencies": {
+ "@babel/core": "^7.20.5",
+ "@babel/eslint-parser": "^7.19.1",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "estraverse": "^5.3.0",
+ "htmlparser2": "^8.0.1",
+ "multi-ini": "^2.3.2"
+ },
+ "devDependencies": {
+ "eslint": "8.29.0",
+ "mocha": "10.1.0"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "peerDependencies": {
+ "@microsoft/eslint-plugin-sdl": "github:mozfreddyb/eslint-plugin-sdl#17b22cd527682108af7a1a4edacf69cb7a9b4a06",
+ "eslint": "^7.23.0 || ^8.0.0",
+ "eslint-config-prettier": "^8.0.0",
+ "eslint-plugin-fetch-options": "^0.0.5",
+ "eslint-plugin-html": "^7.0.0",
+ "eslint-plugin-no-unsanitized": "^4.0.0",
+ "eslint-plugin-prettier": "^3.0.0",
+ "prettier": "^1.19.1"
+ }
+ },
+ "node_modules/@ampproject/remapping": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+ "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
+ "dependencies": {
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "dependencies": {
+ "@babel/highlight": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/compat-data": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz",
+ "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz",
+ "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==",
+ "dependencies": {
+ "@ampproject/remapping": "^2.1.0",
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.5",
+ "@babel/helper-compilation-targets": "^7.20.0",
+ "@babel/helper-module-transforms": "^7.20.2",
+ "@babel/helpers": "^7.20.5",
+ "@babel/parser": "^7.20.5",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.1",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/babel"
+ }
+ },
+ "node_modules/@babel/eslint-parser": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz",
+ "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==",
+ "dependencies": {
+ "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
+ "eslint-visitor-keys": "^2.1.0",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": "^10.13.0 || ^12.13.0 || >=14.0.0"
+ },
+ "peerDependencies": {
+ "@babel/core": ">=7.11.0",
+ "eslint": "^7.5.0 || ^8.0.0"
+ }
+ },
+ "node_modules/@babel/eslint-parser/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@babel/generator": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
+ "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
+ "dependencies": {
+ "@babel/types": "^7.20.5",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "jsesc": "^2.5.1"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/generator/node_modules/@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/helper-compilation-targets": {
+ "version": "7.20.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz",
+ "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==",
+ "dependencies": {
+ "@babel/compat-data": "^7.20.0",
+ "@babel/helper-validator-option": "^7.18.6",
+ "browserslist": "^4.21.3",
+ "semver": "^6.3.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ },
+ "peerDependencies": {
+ "@babel/core": "^7.0.0"
+ }
+ },
+ "node_modules/@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
+ "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-function-name": {
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
+ "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+ "dependencies": {
+ "@babel/template": "^7.18.10",
+ "@babel/types": "^7.19.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-hoist-variables": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
+ "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-module-transforms": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz",
+ "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==",
+ "dependencies": {
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-simple-access": "^7.20.2",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-simple-access": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
+ "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
+ "dependencies": {
+ "@babel/types": "^7.20.2"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
+ "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "dependencies": {
+ "@babel/types": "^7.18.6"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-string-parser": {
+ "version": "7.19.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
+ "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helper-validator-option": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
+ "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/helpers": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
+ "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
+ "dependencies": {
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "dependencies": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/parser": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
+ "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA==",
+ "bin": {
+ "parser": "bin/babel-parser.js"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@babel/runtime": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
+ "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
+ "dependencies": {
+ "regenerator-runtime": "^0.13.11"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/template": {
+ "version": "7.18.10",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
+ "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/parser": "^7.18.10",
+ "@babel/types": "^7.18.10"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/traverse": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
+ "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
+ "dependencies": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.5",
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-function-name": "^7.19.0",
+ "@babel/helper-hoist-variables": "^7.18.6",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/parser": "^7.20.5",
+ "@babel/types": "^7.20.5",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@babel/types": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
+ "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
+ "dependencies": {
+ "@babel/helper-string-parser": "^7.19.4",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "to-fast-properties": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/@eslint/eslintrc": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
+ "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.4.0",
+ "globals": "^13.15.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@eslint/eslintrc/node_modules/globals": {
+ "version": "13.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
+ "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@humanwhocodes/config-array": {
+ "version": "0.11.7",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
+ "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==",
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA==",
+ "engines": {
+ "node": ">=12.22"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/nzakas"
+ }
+ },
+ "node_modules/@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
+ },
+ "node_modules/@jridgewell/gen-mapping": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+ "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+ "dependencies": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw==",
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "node_modules/@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "dependencies": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl": {
+ "version": "0.2.1",
+ "resolved": "git+ssh://git@github.com/mozfreddyb/eslint-plugin-sdl.git#17b22cd527682108af7a1a4edacf69cb7a9b4a06",
+ "integrity": "sha512-OgZ+Oy+AugobKNvEZy0e9pCtp3cNc8OLKeF7cy1u+pwFS0LJic81XEKhWQqd6/vPEkx8m8TJfOF517TIHXVCTA==",
+ "license": "MIT",
+ "peer": true,
+ "dependencies": {
+ "eslint-plugin-node": "11.1.0",
+ "eslint-plugin-react": "7.24.0",
+ "eslint-plugin-security": "1.4.0"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/@babel/code-frame": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
+ "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
+ "peer": true,
+ "dependencies": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/@eslint/eslintrc": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
+ "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==",
+ "peer": true,
+ "dependencies": {
+ "ajv": "^6.12.4",
+ "debug": "^4.1.1",
+ "espree": "^7.3.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^3.13.1",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/@humanwhocodes/config-array": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
+ "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==",
+ "peer": true,
+ "dependencies": {
+ "@humanwhocodes/object-schema": "^1.2.0",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ },
+ "engines": {
+ "node": ">=10.10.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "peer": true,
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "peer": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "peer": true,
+ "dependencies": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "peer": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "peer": true
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint": {
+ "version": "7.32.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz",
+ "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==",
+ "peer": true,
+ "dependencies": {
+ "@babel/code-frame": "7.12.11",
+ "@eslint/eslintrc": "^0.4.3",
+ "@humanwhocodes/config-array": "^0.5.0",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^2.1.0",
+ "eslint-visitor-keys": "^2.0.0",
+ "espree": "^7.3.1",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.1.2",
+ "globals": "^13.6.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.1.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.0",
+ "strip-json-comments": "^3.1.0",
+ "table": "^6.0.9",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-plugin-react": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz",
+ "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==",
+ "peer": true,
+ "dependencies": {
+ "array-includes": "^3.1.3",
+ "array.prototype.flatmap": "^1.2.4",
+ "doctrine": "^2.1.0",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.0.4",
+ "object.entries": "^1.1.4",
+ "object.fromentries": "^2.0.4",
+ "object.values": "^1.1.4",
+ "prop-types": "^15.7.2",
+ "resolve": "^2.0.0-next.3",
+ "string.prototype.matchall": "^4.0.5"
+ },
+ "engines": {
+ "node": ">=4"
+ },
+ "peerDependencies": {
+ "eslint": "^3 || ^4 || ^5 || ^6 || ^7"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-plugin-react/node_modules/doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "peer": true,
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "peer": true,
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "peer": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "peer": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/espree": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
+ "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==",
+ "peer": true,
+ "dependencies": {
+ "acorn": "^7.4.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^1.3.0"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/espree/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "peer": true,
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "peer": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/globals": {
+ "version": "13.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
+ "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
+ "peer": true,
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "peer": true,
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "peer": true,
+ "dependencies": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/resolve": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
+ "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
+ "peer": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "peer": true,
+ "dependencies": {
+ "lru-cache": "^6.0.0"
+ },
+ "bin": {
+ "semver": "bin/semver.js"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/@microsoft/eslint-plugin-sdl/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "peer": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/@nicolo-ribaudo/eslint-scope-5-internals": {
+ "version": "5.1.1-v1",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
+ "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==",
+ "dependencies": {
+ "eslint-scope": "5.1.1"
+ }
+ },
+ "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.0.0"
+ }
+ },
+ "node_modules/@nicolo-ribaudo/eslint-scope-5-internals/node_modules/estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "dependencies": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A==",
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "dependencies": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/acorn": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
+ "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA==",
+ "bin": {
+ "acorn": "bin/acorn"
+ },
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "peerDependencies": {
+ "acorn": "^6.0.0 || ^7.0.0 || ^8.0.0"
+ }
+ },
+ "node_modules/ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "dependencies": {
+ "color-convert": "^1.9.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "dependencies": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "node_modules/array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/array.prototype.flatmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
+ "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "node_modules/binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "dependencies": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "node_modules/braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "dependencies": {
+ "fill-range": "^7.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "node_modules/browserslist": {
+ "version": "4.21.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
+ "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "dependencies": {
+ "caniuse-lite": "^1.0.30001400",
+ "electron-to-chromium": "^1.4.251",
+ "node-releases": "^2.0.6",
+ "update-browserslist-db": "^1.0.9"
+ },
+ "bin": {
+ "browserslist": "cli.js"
+ },
+ "engines": {
+ "node": "^6 || ^7 || ^8 || ^9 || ^10 || ^11 || ^12 || >=13.7"
+ }
+ },
+ "node_modules/call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/caniuse-lite": {
+ "version": "1.0.30001436",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz",
+ "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/caniuse-lite"
+ }
+ ]
+ },
+ "node_modules/chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "dependencies": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "individual",
+ "url": "https://paulmillr.com/funding/"
+ }
+ ],
+ "dependencies": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "engines": {
+ "node": ">= 8.10.0"
+ },
+ "optionalDependencies": {
+ "fsevents": "~2.3.2"
+ }
+ },
+ "node_modules/chokidar/node_modules/glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "dependencies": {
+ "is-glob": "^4.0.1"
+ },
+ "engines": {
+ "node": ">= 6"
+ }
+ },
+ "node_modules/cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "dependencies": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "node_modules/color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "dependencies": {
+ "color-name": "1.1.3"
+ }
+ },
+ "node_modules/color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "node_modules/concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "node_modules/convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
+ },
+ "node_modules/cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "dependencies": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "dependencies": {
+ "ms": "2.1.2"
+ },
+ "engines": {
+ "node": ">=6.0"
+ },
+ "peerDependenciesMeta": {
+ "supports-color": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+ },
+ "node_modules/define-properties": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+ "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "peer": true,
+ "dependencies": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.3.1"
+ }
+ },
+ "node_modules/doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "dependencies": {
+ "esutils": "^2.0.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ },
+ "funding": {
+ "url": "https://github.com/cheeriojs/dom-serializer?sponsor=1"
+ }
+ },
+ "node_modules/domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ]
+ },
+ "node_modules/domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "dependencies": {
+ "domelementtype": "^2.3.0"
+ },
+ "engines": {
+ "node": ">= 4"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domhandler?sponsor=1"
+ }
+ },
+ "node_modules/domutils": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
+ "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+ "dependencies": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/domutils?sponsor=1"
+ }
+ },
+ "node_modules/electron-to-chromium": {
+ "version": "1.4.284",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
+ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
+ },
+ "node_modules/emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "node_modules/enquirer": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "peer": true,
+ "dependencies": {
+ "ansi-colors": "^4.1.1"
+ },
+ "engines": {
+ "node": ">=8.6"
+ }
+ },
+ "node_modules/entities": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
+ "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA==",
+ "engines": {
+ "node": ">=0.12"
+ },
+ "funding": {
+ "url": "https://github.com/fb55/entities?sponsor=1"
+ }
+ },
+ "node_modules/es-abstract": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz",
+ "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.1.3",
+ "get-symbol-description": "^1.0.0",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.2",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trimend": "^1.0.5",
+ "string.prototype.trimstart": "^1.0.5",
+ "unbox-primitive": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/es-shim-unscopables": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+ "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "peer": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ }
+ },
+ "node_modules/es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "peer": true,
+ "dependencies": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg==",
+ "engines": {
+ "node": ">=0.8.0"
+ }
+ },
+ "node_modules/eslint": {
+ "version": "8.29.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz",
+ "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==",
+ "dependencies": {
+ "@eslint/eslintrc": "^1.3.3",
+ "@humanwhocodes/config-array": "^0.11.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.4.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.15.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "bin": {
+ "eslint": "bin/eslint.js"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/eslint-config-prettier": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
+ "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
+ "peer": true,
+ "bin": {
+ "eslint-config-prettier": "bin/cli.js"
+ },
+ "peerDependencies": {
+ "eslint": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint-plugin-es": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz",
+ "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==",
+ "peer": true,
+ "dependencies": {
+ "eslint-utils": "^2.0.0",
+ "regexpp": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=4.19.1"
+ }
+ },
+ "node_modules/eslint-plugin-es/node_modules/eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "peer": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/eslint-plugin-es/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint-plugin-fetch-options": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-fetch-options/-/eslint-plugin-fetch-options-0.0.5.tgz",
+ "integrity": "sha512-ZMxrccsOAZ7uMQ4nMvPJLqLg6oyLF96YOEwTKWAIbDHpwWUp1raXALZom8ikKucaEnhqWSRuBWI8jBXveFwkJg==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.9.0"
+ }
+ },
+ "node_modules/eslint-plugin-html": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-7.1.0.tgz",
+ "integrity": "sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==",
+ "peer": true,
+ "dependencies": {
+ "htmlparser2": "^8.0.1"
+ }
+ },
+ "node_modules/eslint-plugin-no-unsanitized": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.0.2.tgz",
+ "integrity": "sha512-Pry0S9YmHoz8NCEMRQh7N0Yexh2MYCNPIlrV52hTmS7qXnTghWsjXouF08bgsrrZqaW9tt1ZiK3j5NEmPE+EjQ==",
+ "peer": true,
+ "peerDependencies": {
+ "eslint": "^6 || ^7 || ^8"
+ }
+ },
+ "node_modules/eslint-plugin-node": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz",
+ "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==",
+ "peer": true,
+ "dependencies": {
+ "eslint-plugin-es": "^3.0.0",
+ "eslint-utils": "^2.0.0",
+ "ignore": "^5.1.1",
+ "minimatch": "^3.0.4",
+ "resolve": "^1.10.1",
+ "semver": "^6.1.0"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=5.16.0"
+ }
+ },
+ "node_modules/eslint-plugin-node/node_modules/eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "peer": true,
+ "dependencies": {
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/eslint-plugin-node/node_modules/eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true,
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/eslint-plugin-prettier": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz",
+ "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==",
+ "peer": true,
+ "dependencies": {
+ "prettier-linter-helpers": "^1.0.0"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ },
+ "peerDependencies": {
+ "eslint": ">=5.0.0",
+ "prettier": ">=1.13.0"
+ },
+ "peerDependenciesMeta": {
+ "eslint-config-prettier": {
+ "optional": true
+ }
+ }
+ },
+ "node_modules/eslint-plugin-security": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz",
+ "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==",
+ "peer": true,
+ "dependencies": {
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "node_modules/eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "dependencies": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "dependencies": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "engines": {
+ "node": "^10.0.0 || ^12.0.0 || >= 14.0.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ },
+ "peerDependencies": {
+ "eslint": ">=5"
+ }
+ },
+ "node_modules/eslint-utils/node_modules/eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA==",
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/eslint/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/eslint/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "node_modules/eslint/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/globals": {
+ "version": "13.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
+ "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
+ "dependencies": {
+ "type-fest": "^0.20.2"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/eslint/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/eslint/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/espree": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
+ "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+ "dependencies": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ },
+ "engines": {
+ "node": "^12.22.0 || ^14.17.0 || >=16.0.0"
+ },
+ "funding": {
+ "url": "https://opencollective.com/eslint"
+ }
+ },
+ "node_modules/esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "peer": true,
+ "bin": {
+ "esparse": "bin/esparse.js",
+ "esvalidate": "bin/esvalidate.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "dependencies": {
+ "estraverse": "^5.1.0"
+ },
+ "engines": {
+ "node": ">=0.10"
+ }
+ },
+ "node_modules/esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "dependencies": {
+ "estraverse": "^5.2.0"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA==",
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "node_modules/fast-diff": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+ "peer": true
+ },
+ "node_modules/fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "node_modules/fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
+ },
+ "node_modules/fastq": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
+ "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
+ "dependencies": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "node_modules/file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "dependencies": {
+ "flat-cache": "^3.0.4"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "dependencies": {
+ "to-regex-range": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "dependencies": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true,
+ "bin": {
+ "flat": "cli.js"
+ }
+ },
+ "node_modules/flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "dependencies": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ },
+ "engines": {
+ "node": "^10.12.0 || >=12.0.0"
+ }
+ },
+ "node_modules/flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ=="
+ },
+ "node_modules/fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "node_modules/fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "hasInstallScript": true,
+ "optional": true,
+ "os": [
+ "darwin"
+ ],
+ "engines": {
+ "node": "^8.16.0 || ^10.6.0 || >=11.0.0"
+ }
+ },
+ "node_modules/function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "peer": true
+ },
+ "node_modules/function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "peer": true
+ },
+ "node_modules/functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg==",
+ "engines": {
+ "node": ">=6.9.0"
+ }
+ },
+ "node_modules/get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true,
+ "engines": {
+ "node": "6.* || 8.* || >= 10.*"
+ }
+ },
+ "node_modules/get-intrinsic": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
+ "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/get-symbol-description": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+ "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "dependencies": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ },
+ "engines": {
+ "node": "*"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "dependencies": {
+ "is-glob": "^4.0.3"
+ },
+ "engines": {
+ "node": ">=10.13.0"
+ }
+ },
+ "node_modules/globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ=="
+ },
+ "node_modules/has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "peer": true,
+ "dependencies": {
+ "function-bind": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4.0"
+ }
+ },
+ "node_modules/has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "peer": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/has-tostringtag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "peer": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true,
+ "bin": {
+ "he": "bin/he"
+ }
+ },
+ "node_modules/htmlparser2": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz",
+ "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==",
+ "funding": [
+ "https://github.com/fb55/htmlparser2?sponsor=1",
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/fb55"
+ }
+ ],
+ "dependencies": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "entities": "^4.3.0"
+ }
+ },
+ "node_modules/ignore": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
+ "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA==",
+ "engines": {
+ "node": ">= 4"
+ }
+ },
+ "node_modules/import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "dependencies": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA==",
+ "engines": {
+ "node": ">=0.8.19"
+ }
+ },
+ "node_modules/inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "dependencies": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "node_modules/inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "node_modules/internal-slot": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
+ "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
+ "peer": true,
+ "dependencies": {
+ "get-intrinsic": "^1.1.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "peer": true,
+ "dependencies": {
+ "has-bigints": "^1.0.1"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "dependencies": {
+ "binary-extensions": "^2.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "peer": true,
+ "dependencies": {
+ "has": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "peer": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "dependencies": {
+ "is-extglob": "^2.1.1"
+ },
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/is-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.12.0"
+ }
+ },
+ "node_modules/is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "peer": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "peer": true,
+ "dependencies": {
+ "has-tostringtag": "^1.0.0"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "peer": true,
+ "dependencies": {
+ "has-symbols": "^1.0.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "node_modules/js-sdsl": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
+ "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ==",
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/js-sdsl"
+ }
+ },
+ "node_modules/js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "node_modules/js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "dependencies": {
+ "argparse": "^2.0.1"
+ },
+ "bin": {
+ "js-yaml": "bin/js-yaml.js"
+ }
+ },
+ "node_modules/jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA==",
+ "bin": {
+ "jsesc": "bin/jsesc"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "node_modules/json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
+ },
+ "node_modules/json5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA==",
+ "bin": {
+ "json5": "lib/cli.js"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/jsx-ast-utils": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
+ "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
+ "peer": true,
+ "dependencies": {
+ "array-includes": "^3.1.5",
+ "object.assign": "^4.1.3"
+ },
+ "engines": {
+ "node": ">=4.0"
+ }
+ },
+ "node_modules/levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "dependencies": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "dependencies": {
+ "p-locate": "^5.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "node_modules/lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+ },
+ "node_modules/lodash.truncate": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
+ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
+ "peer": true
+ },
+ "node_modules/log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "dependencies": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/log-symbols/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/chalk?sponsor=1"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/log-symbols/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/log-symbols/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/log-symbols/node_modules/supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "peer": true,
+ "dependencies": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ },
+ "bin": {
+ "loose-envify": "cli.js"
+ }
+ },
+ "node_modules/lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "peer": true,
+ "dependencies": {
+ "yallist": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "dependencies": {
+ "brace-expansion": "^1.1.7"
+ },
+ "engines": {
+ "node": "*"
+ }
+ },
+ "node_modules/mocha": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
+ "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
+ "dev": true,
+ "dependencies": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "bin": {
+ "_mocha": "bin/_mocha",
+ "mocha": "bin/mocha.js"
+ },
+ "engines": {
+ "node": ">= 14.0.0"
+ },
+ "funding": {
+ "type": "opencollective",
+ "url": "https://opencollective.com/mochajs"
+ }
+ },
+ "node_modules/mocha/node_modules/brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "dependencies": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "node_modules/mocha/node_modules/escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/mocha/node_modules/has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true,
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/mocha/node_modules/minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "dependencies": {
+ "brace-expansion": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/mocha/node_modules/ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "node_modules/mocha/node_modules/supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "dependencies": {
+ "has-flag": "^4.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/supports-color?sponsor=1"
+ }
+ },
+ "node_modules/ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "node_modules/multi-ini": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/multi-ini/-/multi-ini-2.3.2.tgz",
+ "integrity": "sha512-zuznIotGjtc8AXfWwX5/pfQI6JadxR/kN7zA1W8qqomk/7zKHMW54ik052dqV3bPzWLucysvPgJXEySsctUUWQ==",
+ "dependencies": {
+ "@babel/runtime": "^7.0.0",
+ "lodash": "^4.0.0"
+ }
+ },
+ "node_modules/nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true,
+ "bin": {
+ "nanoid": "bin/nanoid.cjs"
+ },
+ "engines": {
+ "node": "^10 || ^12 || ^13.7 || ^14 || >=15.0.1"
+ }
+ },
+ "node_modules/natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
+ },
+ "node_modules/node-releases": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
+ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg=="
+ },
+ "node_modules/normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/object-inspect": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+ "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
+ "peer": true,
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.entries": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
+ "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ }
+ },
+ "node_modules/object.fromentries": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
+ "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "dependencies": {
+ "wrappy": "1"
+ }
+ },
+ "node_modules/optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "dependencies": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "dependencies": {
+ "yocto-queue": "^0.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "dependencies": {
+ "p-limit": "^3.0.2"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "dependencies": {
+ "callsites": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "peer": true
+ },
+ "node_modules/picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "node_modules/picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true,
+ "engines": {
+ "node": ">=8.6"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/jonschlinkert"
+ }
+ },
+ "node_modules/prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==",
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/prettier": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
+ "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
+ "peer": true,
+ "bin": {
+ "prettier": "bin-prettier.js"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "peer": true,
+ "dependencies": {
+ "fast-diff": "^1.1.2"
+ },
+ "engines": {
+ "node": ">=6.0.0"
+ }
+ },
+ "node_modules/progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.4.0"
+ }
+ },
+ "node_modules/prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "peer": true,
+ "dependencies": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "node_modules/punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A==",
+ "engines": {
+ "node": ">=6"
+ }
+ },
+ "node_modules/queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "dependencies": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "node_modules/react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "peer": true
+ },
+ "node_modules/readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "dependencies": {
+ "picomatch": "^2.2.1"
+ },
+ "engines": {
+ "node": ">=8.10.0"
+ }
+ },
+ "node_modules/regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ },
+ "node_modules/regexp.prototype.flags": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+ "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
+ },
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/mysticatea"
+ }
+ },
+ "node_modules/require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "peer": true,
+ "dependencies": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ },
+ "bin": {
+ "resolve": "bin/resolve"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "peer": true,
+ "engines": {
+ "node": ">=0.12"
+ }
+ },
+ "node_modules/reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw==",
+ "engines": {
+ "iojs": ">=1.0.0",
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "dependencies": {
+ "glob": "^7.1.3"
+ },
+ "bin": {
+ "rimraf": "bin.js"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/isaacs"
+ }
+ },
+ "node_modules/run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ],
+ "dependencies": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "node_modules/safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true,
+ "funding": [
+ {
+ "type": "github",
+ "url": "https://github.com/sponsors/feross"
+ },
+ {
+ "type": "patreon",
+ "url": "https://www.patreon.com/feross"
+ },
+ {
+ "type": "consulting",
+ "url": "https://feross.org/support"
+ }
+ ]
+ },
+ "node_modules/safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==",
+ "peer": true,
+ "dependencies": {
+ "ret": "~0.1.10"
+ }
+ },
+ "node_modules/safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw==",
+ "bin": {
+ "semver": "bin/semver.js"
+ }
+ },
+ "node_modules/serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "dependencies": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "node_modules/shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "dependencies": {
+ "shebang-regex": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A==",
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "peer": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/slice-ansi?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "peer": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "peer": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/slice-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "peer": true
+ },
+ "node_modules/sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "peer": true
+ },
+ "node_modules/string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "dependencies": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/string.prototype.matchall": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
+ "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "dependencies": {
+ "ansi-regex": "^5.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ }
+ },
+ "node_modules/strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig==",
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "dependencies": {
+ "has-flag": "^3.0.0"
+ },
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "peer": true,
+ "engines": {
+ "node": ">= 0.4"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/table": {
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
+ "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==",
+ "peer": true,
+ "dependencies": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "engines": {
+ "node": ">=10.0.0"
+ }
+ },
+ "node_modules/table/node_modules/ajv": {
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
+ "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
+ "peer": true,
+ "dependencies": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ },
+ "funding": {
+ "type": "github",
+ "url": "https://github.com/sponsors/epoberezkin"
+ }
+ },
+ "node_modules/table/node_modules/json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "peer": true
+ },
+ "node_modules/text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+ },
+ "node_modules/to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog==",
+ "engines": {
+ "node": ">=4"
+ }
+ },
+ "node_modules/to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "dependencies": {
+ "is-number": "^7.0.0"
+ },
+ "engines": {
+ "node": ">=8.0"
+ }
+ },
+ "node_modules/type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "dependencies": {
+ "prelude-ls": "^1.2.1"
+ },
+ "engines": {
+ "node": ">= 0.8.0"
+ }
+ },
+ "node_modules/type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ },
+ "node_modules/unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "peer": true,
+ "dependencies": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/update-browserslist-db": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "funding": [
+ {
+ "type": "opencollective",
+ "url": "https://opencollective.com/browserslist"
+ },
+ {
+ "type": "tidelift",
+ "url": "https://tidelift.com/funding/github/npm/browserslist"
+ }
+ ],
+ "dependencies": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ },
+ "bin": {
+ "browserslist-lint": "cli.js"
+ },
+ "peerDependencies": {
+ "browserslist": ">= 4.21.0"
+ }
+ },
+ "node_modules/uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "dependencies": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "node_modules/v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "peer": true
+ },
+ "node_modules/which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "dependencies": {
+ "isexe": "^2.0.0"
+ },
+ "bin": {
+ "node-which": "bin/node-which"
+ },
+ "engines": {
+ "node": ">= 8"
+ }
+ },
+ "node_modules/which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "peer": true,
+ "dependencies": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/ljharb"
+ }
+ },
+ "node_modules/word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ==",
+ "engines": {
+ "node": ">=0.10.0"
+ }
+ },
+ "node_modules/workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
+ "node_modules/wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "dependencies": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/wrap-ansi?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "dependencies": {
+ "color-convert": "^2.0.1"
+ },
+ "engines": {
+ "node": ">=8"
+ },
+ "funding": {
+ "url": "https://github.com/chalk/ansi-styles?sponsor=1"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "dependencies": {
+ "color-name": "~1.1.4"
+ },
+ "engines": {
+ "node": ">=7.0.0"
+ }
+ },
+ "node_modules/wrap-ansi/node_modules/color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "node_modules/wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "node_modules/y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "peer": true
+ },
+ "node_modules/yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "dependencies": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true,
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "dependencies": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ },
+ "engines": {
+ "node": ">=10"
+ }
+ },
+ "node_modules/yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q==",
+ "engines": {
+ "node": ">=10"
+ },
+ "funding": {
+ "url": "https://github.com/sponsors/sindresorhus"
+ }
+ }
+ },
+ "dependencies": {
+ "@ampproject/remapping": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/@ampproject/remapping/-/remapping-2.2.0.tgz",
+ "integrity": "sha512-qRmjj8nj9qmLTQXXmaR1cck3UXSRMPrbsLJAasZpF+t3riI71BXed5ebIOYwQntykeZuhjsdweEc9BxH5Jc26w==",
+ "requires": {
+ "@jridgewell/gen-mapping": "^0.1.0",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ },
+ "@babel/code-frame": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.18.6.tgz",
+ "integrity": "sha512-TDCmlK5eOvH+eH7cdAFlNXeVJqWIQ7gW9tY1GJIpUtFb6CmjVyq2VM3u71bOyR8CRihcCgMUYoDNyLXao3+70Q==",
+ "requires": {
+ "@babel/highlight": "^7.18.6"
+ }
+ },
+ "@babel/compat-data": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/compat-data/-/compat-data-7.20.5.tgz",
+ "integrity": "sha512-KZXo2t10+/jxmkhNXc7pZTqRvSOIvVv/+lJwHS+B2rErwOyjuVRh60yVpb7liQ1U5t7lLJ1bz+t8tSypUZdm0g=="
+ },
+ "@babel/core": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/core/-/core-7.20.5.tgz",
+ "integrity": "sha512-UdOWmk4pNWTm/4DlPUl/Pt4Gz4rcEMb7CY0Y3eJl5Yz1vI8ZJGmHWaVE55LoxRjdpx0z259GE9U5STA9atUinQ==",
+ "requires": {
+ "@ampproject/remapping": "^2.1.0",
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.5",
+ "@babel/helper-compilation-targets": "^7.20.0",
+ "@babel/helper-module-transforms": "^7.20.2",
+ "@babel/helpers": "^7.20.5",
+ "@babel/parser": "^7.20.5",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5",
+ "convert-source-map": "^1.7.0",
+ "debug": "^4.1.0",
+ "gensync": "^1.0.0-beta.2",
+ "json5": "^2.2.1",
+ "semver": "^6.3.0"
+ }
+ },
+ "@babel/eslint-parser": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/eslint-parser/-/eslint-parser-7.19.1.tgz",
+ "integrity": "sha512-AqNf2QWt1rtu2/1rLswy6CDP7H9Oh3mMhk177Y67Rg8d7RD9WfOLLv8CGn6tisFvS2htm86yIe1yLF6I1UDaGQ==",
+ "requires": {
+ "@nicolo-ribaudo/eslint-scope-5-internals": "5.1.1-v1",
+ "eslint-visitor-keys": "^2.1.0",
+ "semver": "^6.3.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="
+ }
+ }
+ },
+ "@babel/generator": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/generator/-/generator-7.20.5.tgz",
+ "integrity": "sha512-jl7JY2Ykn9S0yj4DQP82sYvPU+T3g0HFcWTqDLqiuA9tGRNIj9VfbtXGAYTTkyNEnQk1jkMGOdYka8aG/lulCA==",
+ "requires": {
+ "@babel/types": "^7.20.5",
+ "@jridgewell/gen-mapping": "^0.3.2",
+ "jsesc": "^2.5.1"
+ },
+ "dependencies": {
+ "@jridgewell/gen-mapping": {
+ "version": "0.3.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.3.2.tgz",
+ "integrity": "sha512-mh65xKQAzI6iBcFzwv28KVWSmCkdRBWoOh+bYQGW3+6OZvbbN3TqMGo5hqYxQniRcH9F2VZIoJCm4pa3BPDK/A==",
+ "requires": {
+ "@jridgewell/set-array": "^1.0.1",
+ "@jridgewell/sourcemap-codec": "^1.4.10",
+ "@jridgewell/trace-mapping": "^0.3.9"
+ }
+ }
+ }
+ },
+ "@babel/helper-compilation-targets": {
+ "version": "7.20.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-compilation-targets/-/helper-compilation-targets-7.20.0.tgz",
+ "integrity": "sha512-0jp//vDGp9e8hZzBc6N/KwA5ZK3Wsm/pfm4CrY7vzegkVxc65SgSn6wYOnwHe9Js9HRQ1YTCKLGPzDtaS3RoLQ==",
+ "requires": {
+ "@babel/compat-data": "^7.20.0",
+ "@babel/helper-validator-option": "^7.18.6",
+ "browserslist": "^4.21.3",
+ "semver": "^6.3.0"
+ }
+ },
+ "@babel/helper-environment-visitor": {
+ "version": "7.18.9",
+ "resolved": "https://registry.npmjs.org/@babel/helper-environment-visitor/-/helper-environment-visitor-7.18.9.tgz",
+ "integrity": "sha512-3r/aACDJ3fhQ/EVgFy0hpj8oHyHpQc+LPtJoY9SzTThAsStm4Ptegq92vqKoE3vD706ZVFWITnMnxucw+S9Ipg=="
+ },
+ "@babel/helper-function-name": {
+ "version": "7.19.0",
+ "resolved": "https://registry.npmjs.org/@babel/helper-function-name/-/helper-function-name-7.19.0.tgz",
+ "integrity": "sha512-WAwHBINyrpqywkUH0nTnNgI5ina5TFn85HKS0pbPDfxFfhyR/aNQEn4hGi1P1JyT//I0t4OgXUlofzWILRvS5w==",
+ "requires": {
+ "@babel/template": "^7.18.10",
+ "@babel/types": "^7.19.0"
+ }
+ },
+ "@babel/helper-hoist-variables": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-hoist-variables/-/helper-hoist-variables-7.18.6.tgz",
+ "integrity": "sha512-UlJQPkFqFULIcyW5sbzgbkxn2FKRgwWiRexcuaR8RNJRy8+LLveqPjwZV/bwrLZCN0eUHD/x8D0heK1ozuoo6Q==",
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-module-imports": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-imports/-/helper-module-imports-7.18.6.tgz",
+ "integrity": "sha512-0NFvs3VkuSYbFi1x2Vd6tKrywq+z/cLeYC/RJNFrIX/30Bf5aiGYbtvGXolEktzJH8o5E5KJ3tT+nkxuuZFVlA==",
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-module-transforms": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-module-transforms/-/helper-module-transforms-7.20.2.tgz",
+ "integrity": "sha512-zvBKyJXRbmK07XhMuujYoJ48B5yvvmM6+wcpv6Ivj4Yg6qO7NOZOSnvZN9CRl1zz1Z4cKf8YejmCMh8clOoOeA==",
+ "requires": {
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-module-imports": "^7.18.6",
+ "@babel/helper-simple-access": "^7.20.2",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.1",
+ "@babel/types": "^7.20.2"
+ }
+ },
+ "@babel/helper-simple-access": {
+ "version": "7.20.2",
+ "resolved": "https://registry.npmjs.org/@babel/helper-simple-access/-/helper-simple-access-7.20.2.tgz",
+ "integrity": "sha512-+0woI/WPq59IrqDYbVGfshjT5Dmk/nnbdpcF8SnMhhXObpTq2KNBdLFRFrkVdbDOyUmHBCxzm5FHV1rACIkIbA==",
+ "requires": {
+ "@babel/types": "^7.20.2"
+ }
+ },
+ "@babel/helper-split-export-declaration": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-split-export-declaration/-/helper-split-export-declaration-7.18.6.tgz",
+ "integrity": "sha512-bde1etTx6ZyTmobl9LLMMQsaizFVZrquTEHOqKeQESMKo4PlObf+8+JA25ZsIpZhT/WEd39+vOdLXAFG/nELpA==",
+ "requires": {
+ "@babel/types": "^7.18.6"
+ }
+ },
+ "@babel/helper-string-parser": {
+ "version": "7.19.4",
+ "resolved": "https://registry.npmjs.org/@babel/helper-string-parser/-/helper-string-parser-7.19.4.tgz",
+ "integrity": "sha512-nHtDoQcuqFmwYNYPz3Rah5ph2p8PFeFCsZk9A/48dPc/rGocJ5J3hAAZ7pb76VWX3fZKu+uEr/FhH5jLx7umrw=="
+ },
+ "@babel/helper-validator-identifier": {
+ "version": "7.19.1",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-identifier/-/helper-validator-identifier-7.19.1.tgz",
+ "integrity": "sha512-awrNfaMtnHUr653GgGEs++LlAvW6w+DcPrOliSMXWCKo597CwL5Acf/wWdNkf/tfEQE3mjkeD1YOVZOUV/od1w=="
+ },
+ "@babel/helper-validator-option": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/helper-validator-option/-/helper-validator-option-7.18.6.tgz",
+ "integrity": "sha512-XO7gESt5ouv/LRJdrVjkShckw6STTaB7l9BrpBaAHDeF5YZT+01PCwmR0SJHnkW6i8OwW/EVWRShfi4j2x+KQw=="
+ },
+ "@babel/helpers": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/helpers/-/helpers-7.20.6.tgz",
+ "integrity": "sha512-Pf/OjgfgFRW5bApskEz5pvidpim7tEDPlFtKcNRXWmfHGn9IEI2W2flqRQXTFb7gIPTyK++N6rVHuwKut4XK6w==",
+ "requires": {
+ "@babel/template": "^7.18.10",
+ "@babel/traverse": "^7.20.5",
+ "@babel/types": "^7.20.5"
+ }
+ },
+ "@babel/highlight": {
+ "version": "7.18.6",
+ "resolved": "https://registry.npmjs.org/@babel/highlight/-/highlight-7.18.6.tgz",
+ "integrity": "sha512-u7stbOuYjaPezCuLj29hNW1v64M2Md2qupEKP1fHc7WdOA3DgLh37suiSrZYY7haUB7iBeQZ9P1uiRF359do3g==",
+ "requires": {
+ "@babel/helper-validator-identifier": "^7.18.6",
+ "chalk": "^2.0.0",
+ "js-tokens": "^4.0.0"
+ }
+ },
+ "@babel/parser": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/parser/-/parser-7.20.5.tgz",
+ "integrity": "sha512-r27t/cy/m9uKLXQNWWebeCUHgnAZq0CpG1OwKRxzJMP1vpSU4bSIK2hq+/cp0bQxetkXx38n09rNu8jVkcK/zA=="
+ },
+ "@babel/runtime": {
+ "version": "7.20.6",
+ "resolved": "https://registry.npmjs.org/@babel/runtime/-/runtime-7.20.6.tgz",
+ "integrity": "sha512-Q+8MqP7TiHMWzSfwiJwXCjyf4GYA4Dgw3emg/7xmwsdLJOZUp+nMqcOwOzzYheuM1rhDu8FSj2l0aoMygEuXuA==",
+ "requires": {
+ "regenerator-runtime": "^0.13.11"
+ }
+ },
+ "@babel/template": {
+ "version": "7.18.10",
+ "resolved": "https://registry.npmjs.org/@babel/template/-/template-7.18.10.tgz",
+ "integrity": "sha512-TI+rCtooWHr3QJ27kJxfjutghu44DLnasDMwpDqCXVTal9RLp3RSYNh4NdBrRP2cQAoG9A8juOQl6P6oZG4JxA==",
+ "requires": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/parser": "^7.18.10",
+ "@babel/types": "^7.18.10"
+ }
+ },
+ "@babel/traverse": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/traverse/-/traverse-7.20.5.tgz",
+ "integrity": "sha512-WM5ZNN3JITQIq9tFZaw1ojLU3WgWdtkxnhM1AegMS+PvHjkM5IXjmYEGY7yukz5XS4sJyEf2VzWjI8uAavhxBQ==",
+ "requires": {
+ "@babel/code-frame": "^7.18.6",
+ "@babel/generator": "^7.20.5",
+ "@babel/helper-environment-visitor": "^7.18.9",
+ "@babel/helper-function-name": "^7.19.0",
+ "@babel/helper-hoist-variables": "^7.18.6",
+ "@babel/helper-split-export-declaration": "^7.18.6",
+ "@babel/parser": "^7.20.5",
+ "@babel/types": "^7.20.5",
+ "debug": "^4.1.0",
+ "globals": "^11.1.0"
+ }
+ },
+ "@babel/types": {
+ "version": "7.20.5",
+ "resolved": "https://registry.npmjs.org/@babel/types/-/types-7.20.5.tgz",
+ "integrity": "sha512-c9fst/h2/dcF7H+MJKZ2T0KjEQ8hY/BNnDk/H3XY8C4Aw/eWQXWn/lWntHF9ooUBnGmEvbfGrTgLWc+um0YDUg==",
+ "requires": {
+ "@babel/helper-string-parser": "^7.19.4",
+ "@babel/helper-validator-identifier": "^7.19.1",
+ "to-fast-properties": "^2.0.0"
+ }
+ },
+ "@eslint/eslintrc": {
+ "version": "1.3.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-1.3.3.tgz",
+ "integrity": "sha512-uj3pT6Mg+3t39fvLrj8iuCIJ38zKO9FpGtJ4BBJebJhEwjoT+KLVNCcHT5QC9NGRIEi7fZ0ZR8YRb884auB4Lg==",
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.3.2",
+ "espree": "^9.4.0",
+ "globals": "^13.15.0",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^4.1.0",
+ "minimatch": "^3.1.2",
+ "strip-json-comments": "^3.1.1"
+ },
+ "dependencies": {
+ "globals": {
+ "version": "13.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
+ "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ }
+ }
+ },
+ "@humanwhocodes/config-array": {
+ "version": "0.11.7",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.11.7.tgz",
+ "integrity": "sha512-kBbPWzN8oVMLb0hOUYXhmxggL/1cJE6ydvjDIGi9EnAGUyA7cLVKQg+d/Dsm+KZwx2czGHrCmMVLiyg8s5JPKw==",
+ "requires": {
+ "@humanwhocodes/object-schema": "^1.2.1",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.5"
+ }
+ },
+ "@humanwhocodes/module-importer": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/module-importer/-/module-importer-1.0.1.tgz",
+ "integrity": "sha512-bxveV4V8v5Yb4ncFTT3rPSgZBOpCkjfK0y4oVVVJwIuDVBRMDXrPyXRL988i5ap9m9bnyEEjWfm5WkBmtffLfA=="
+ },
+ "@humanwhocodes/object-schema": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/object-schema/-/object-schema-1.2.1.tgz",
+ "integrity": "sha512-ZnQMnLV4e7hDlUvw8H+U8ASL02SS2Gn6+9Ac3wGGLIe7+je2AeAOxPY+izIPJDfFDb7eDjev0Us8MO1iFRN8hA=="
+ },
+ "@jridgewell/gen-mapping": {
+ "version": "0.1.1",
+ "resolved": "https://registry.npmjs.org/@jridgewell/gen-mapping/-/gen-mapping-0.1.1.tgz",
+ "integrity": "sha512-sQXCasFk+U8lWYEe66WxRDOE9PjVz4vSM51fTu3Hw+ClTpUSQb718772vH3pyS5pShp6lvQM7SxgIDXXXmOX7w==",
+ "requires": {
+ "@jridgewell/set-array": "^1.0.0",
+ "@jridgewell/sourcemap-codec": "^1.4.10"
+ }
+ },
+ "@jridgewell/resolve-uri": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/@jridgewell/resolve-uri/-/resolve-uri-3.1.0.tgz",
+ "integrity": "sha512-F2msla3tad+Mfht5cJq7LSXcdudKTWCVYUgw6pLFOOHSTtZlj6SWNYAp+AhuqLmWdBO2X5hPrLcu8cVP8fy28w=="
+ },
+ "@jridgewell/set-array": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/@jridgewell/set-array/-/set-array-1.1.2.tgz",
+ "integrity": "sha512-xnkseuNADM0gt2bs+BvhO0p78Mk762YnZdsuzFV018NoG1Sj1SCQvpSqa7XUaTam5vAGasABV9qXASMKnFMwMw=="
+ },
+ "@jridgewell/sourcemap-codec": {
+ "version": "1.4.14",
+ "resolved": "https://registry.npmjs.org/@jridgewell/sourcemap-codec/-/sourcemap-codec-1.4.14.tgz",
+ "integrity": "sha512-XPSJHWmi394fuUuzDnGz1wiKqWfo1yXecHQMRf2l6hztTO+nPru658AyDngaBe7isIxEkRsPR3FZh+s7iVa4Uw=="
+ },
+ "@jridgewell/trace-mapping": {
+ "version": "0.3.17",
+ "resolved": "https://registry.npmjs.org/@jridgewell/trace-mapping/-/trace-mapping-0.3.17.tgz",
+ "integrity": "sha512-MCNzAp77qzKca9+W/+I0+sEpaUnZoeasnghNeVc41VZCEKaCH73Vq3BZZ/SzWIgrqE4H4ceI+p+b6C0mHf9T4g==",
+ "requires": {
+ "@jridgewell/resolve-uri": "3.1.0",
+ "@jridgewell/sourcemap-codec": "1.4.14"
+ }
+ },
+ "@microsoft/eslint-plugin-sdl": {
+ "version": "git+ssh://git@github.com/mozfreddyb/eslint-plugin-sdl.git#17b22cd527682108af7a1a4edacf69cb7a9b4a06",
+ "integrity": "sha512-OgZ+Oy+AugobKNvEZy0e9pCtp3cNc8OLKeF7cy1u+pwFS0LJic81XEKhWQqd6/vPEkx8m8TJfOF517TIHXVCTA==",
+ "from": "@microsoft/eslint-plugin-sdl@github:mozfreddyb/eslint-plugin-sdl#17b22cd527682108af7a1a4edacf69cb7a9b4a06",
+ "peer": true,
+ "requires": {
+ "eslint-plugin-node": "11.1.0",
+ "eslint-plugin-react": "7.24.0",
+ "eslint-plugin-security": "1.4.0"
+ },
+ "dependencies": {
+ "@babel/code-frame": {
+ "version": "7.12.11",
+ "resolved": "https://registry.npmjs.org/@babel/code-frame/-/code-frame-7.12.11.tgz",
+ "integrity": "sha512-Zt1yodBx1UcyiePMSkWnU4hPqhwq7hGi2nFL1LeA3EUl+q2LQx16MISgJ0+z7dnmgvP9QtIleuETGOiOH1RcIw==",
+ "peer": true,
+ "requires": {
+ "@babel/highlight": "^7.10.4"
+ }
+ },
+ "@eslint/eslintrc": {
+ "version": "0.4.3",
+ "resolved": "https://registry.npmjs.org/@eslint/eslintrc/-/eslintrc-0.4.3.tgz",
+ "integrity": "sha512-J6KFFz5QCYUJq3pf0mjEcCJVERbzv71PUIDczuh9JkwGEzced6CO5ADLHB1rbf/+oPBtoPfMYNOpGDzCANlbXw==",
+ "peer": true,
+ "requires": {
+ "ajv": "^6.12.4",
+ "debug": "^4.1.1",
+ "espree": "^7.3.0",
+ "globals": "^13.9.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.2.1",
+ "js-yaml": "^3.13.1",
+ "minimatch": "^3.0.4",
+ "strip-json-comments": "^3.1.1"
+ }
+ },
+ "@humanwhocodes/config-array": {
+ "version": "0.5.0",
+ "resolved": "https://registry.npmjs.org/@humanwhocodes/config-array/-/config-array-0.5.0.tgz",
+ "integrity": "sha512-FagtKFz74XrTl7y6HCzQpwDfXP0yhxe9lHLD1UZxjvZIcbyRz8zTFF/yYNfSfzU414eDwZ1SrO0Qvtyf+wFMQg==",
+ "peer": true,
+ "requires": {
+ "@humanwhocodes/object-schema": "^1.2.0",
+ "debug": "^4.1.1",
+ "minimatch": "^3.0.4"
+ }
+ },
+ "acorn": {
+ "version": "7.4.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-7.4.1.tgz",
+ "integrity": "sha512-nQyp0o1/mNdbTO1PO6kHkwSrmgZ0MT/jCCpNiwbUjGoRN4dlBhqJtoQuCnEOKzgTVwg0ZWiCoQy6SxMebQVh8A==",
+ "peer": true
+ },
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "peer": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "argparse": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-1.0.10.tgz",
+ "integrity": "sha512-o5Roy6tNG4SL/FOkCAN6RzjiakZS25RLYFrcMttJqbdd8BWrnA+fGz57iN5Pb06pvBGvl5gQ0B48dJlslXvoTg==",
+ "peer": true,
+ "requires": {
+ "sprintf-js": "~1.0.2"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "peer": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "peer": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "peer": true
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "peer": true
+ },
+ "eslint": {
+ "version": "7.32.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-7.32.0.tgz",
+ "integrity": "sha512-VHZ8gX+EDfz+97jGcgyGCyRia/dPOd6Xh9yPv8Bl1+SoaIwD+a/vlrOmGRUyOYu7MwUhc7CxqeaDZU13S4+EpA==",
+ "peer": true,
+ "requires": {
+ "@babel/code-frame": "7.12.11",
+ "@eslint/eslintrc": "^0.4.3",
+ "@humanwhocodes/config-array": "^0.5.0",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.0.1",
+ "doctrine": "^3.0.0",
+ "enquirer": "^2.3.5",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^5.1.1",
+ "eslint-utils": "^2.1.0",
+ "eslint-visitor-keys": "^2.0.0",
+ "espree": "^7.3.1",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "functional-red-black-tree": "^1.0.1",
+ "glob-parent": "^5.1.2",
+ "globals": "^13.6.0",
+ "ignore": "^4.0.6",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "js-yaml": "^3.13.1",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.0.4",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "progress": "^2.0.0",
+ "regexpp": "^3.1.0",
+ "semver": "^7.2.1",
+ "strip-ansi": "^6.0.0",
+ "strip-json-comments": "^3.1.0",
+ "table": "^6.0.9",
+ "text-table": "^0.2.0",
+ "v8-compile-cache": "^2.0.3"
+ }
+ },
+ "eslint-plugin-react": {
+ "version": "7.24.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-react/-/eslint-plugin-react-7.24.0.tgz",
+ "integrity": "sha512-KJJIx2SYx7PBx3ONe/mEeMz4YE0Lcr7feJTCMyyKb/341NcjuAgim3Acgan89GfPv7nxXK2+0slu0CWXYM4x+Q==",
+ "peer": true,
+ "requires": {
+ "array-includes": "^3.1.3",
+ "array.prototype.flatmap": "^1.2.4",
+ "doctrine": "^2.1.0",
+ "has": "^1.0.3",
+ "jsx-ast-utils": "^2.4.1 || ^3.0.0",
+ "minimatch": "^3.0.4",
+ "object.entries": "^1.1.4",
+ "object.fromentries": "^2.0.4",
+ "object.values": "^1.1.4",
+ "prop-types": "^15.7.2",
+ "resolve": "^2.0.0-next.3",
+ "string.prototype.matchall": "^4.0.5"
+ },
+ "dependencies": {
+ "doctrine": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-2.1.0.tgz",
+ "integrity": "sha512-35mSku4ZXK0vfCuHEDAwt55dg2jNajHZ1odvF+8SSr82EsZY4QmXfuWso8oEd8zRhVObSN18aM0CjSdoBX7zIw==",
+ "peer": true,
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ }
+ }
+ },
+ "eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "peer": true,
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "peer": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true
+ }
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw==",
+ "peer": true
+ },
+ "espree": {
+ "version": "7.3.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-7.3.1.tgz",
+ "integrity": "sha512-v3JCNCE64umkFpmkFGqzVKsOT0tN1Zr+ueqLZfpV1Ob8e+CEgPWa+OxCoGH3tnhimMKIaBm4m/vaRpJ/krRz2g==",
+ "peer": true,
+ "requires": {
+ "acorn": "^7.4.0",
+ "acorn-jsx": "^5.3.1",
+ "eslint-visitor-keys": "^1.3.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true
+ }
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw==",
+ "peer": true
+ },
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "peer": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ },
+ "globals": {
+ "version": "13.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
+ "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
+ "peer": true,
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "peer": true
+ },
+ "ignore": {
+ "version": "4.0.6",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-4.0.6.tgz",
+ "integrity": "sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg==",
+ "peer": true
+ },
+ "js-yaml": {
+ "version": "3.14.1",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-3.14.1.tgz",
+ "integrity": "sha512-okMH7OXXJ7YrN9Ok3/SXrnu4iX9yOk+25nqX4imS2npuvTYDmo/QEZoqwZkYaIDk3jVvBOTOIEgEhaLOynBS9g==",
+ "peer": true,
+ "requires": {
+ "argparse": "^1.0.7",
+ "esprima": "^4.0.0"
+ }
+ },
+ "resolve": {
+ "version": "2.0.0-next.4",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-2.0.0-next.4.tgz",
+ "integrity": "sha512-iMDbmAWtfU+MHpxt/I5iWI7cY6YVEZUQ3MBgPQ++XD1PELuJHIl82xBmObyP2KyQmkNB2dsqF7seoQQiAn5yDQ==",
+ "peer": true,
+ "requires": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "semver": {
+ "version": "7.3.8",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-7.3.8.tgz",
+ "integrity": "sha512-NB1ctGL5rlHrPJtFDVIVzTyQylMLu9N9VICA6HSFJo8MCGVTMW6gfpicwKmmK/dAjTOrqu5l63JJOpDSrAis3A==",
+ "peer": true,
+ "requires": {
+ "lru-cache": "^6.0.0"
+ }
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "peer": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "@nicolo-ribaudo/eslint-scope-5-internals": {
+ "version": "5.1.1-v1",
+ "resolved": "https://registry.npmjs.org/@nicolo-ribaudo/eslint-scope-5-internals/-/eslint-scope-5-internals-5.1.1-v1.tgz",
+ "integrity": "sha512-54/JRvkLIzzDWshCWfuhadfrfZVPiElY8Fcgmg1HroEly/EDSszzhBAsarCux+D/kOslTRquNzuyGSmUSTTHGg==",
+ "requires": {
+ "eslint-scope": "5.1.1"
+ },
+ "dependencies": {
+ "eslint-scope": {
+ "version": "5.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-5.1.1.tgz",
+ "integrity": "sha512-2NxwbF/hZ0KpepYN0cNbo+FN6XoK7GaHlQhgx/hIZl6Va0bF45RQOOwhLIy8lQDbuCiadSLCBnH2CFYquit5bw==",
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^4.1.1"
+ }
+ },
+ "estraverse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-4.3.0.tgz",
+ "integrity": "sha512-39nnKffWz8xN1BU/2c79n9nB9HDzo0niYUqx6xyqUnyoAnQyyWpOTdZEeiCch8BBu515t4wp9ZmgVfVhn9EBpw=="
+ }
+ }
+ },
+ "@nodelib/fs.scandir": {
+ "version": "2.1.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.scandir/-/fs.scandir-2.1.5.tgz",
+ "integrity": "sha512-vq24Bq3ym5HEQm2NKCr3yXDwjc7vTsEThRDnkp2DK9p1uqLR+DHurm/NOTo0KG7HYHU7eppKZj3MyqYuMBf62g==",
+ "requires": {
+ "@nodelib/fs.stat": "2.0.5",
+ "run-parallel": "^1.1.9"
+ }
+ },
+ "@nodelib/fs.stat": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.stat/-/fs.stat-2.0.5.tgz",
+ "integrity": "sha512-RkhPPp2zrqDAQA/2jNhnztcPAlv64XdhIp7a7454A5ovI7Bukxgt7MX7udwAu3zg1DcpPU0rz3VV1SeaqvY4+A=="
+ },
+ "@nodelib/fs.walk": {
+ "version": "1.2.8",
+ "resolved": "https://registry.npmjs.org/@nodelib/fs.walk/-/fs.walk-1.2.8.tgz",
+ "integrity": "sha512-oGB+UxlgWcgQkgwo8GcEGwemoTFt3FIO9ababBmaGwXIoBKZ+GTy0pP185beGg7Llih/NSHSV2XAs1lnznocSg==",
+ "requires": {
+ "@nodelib/fs.scandir": "2.1.5",
+ "fastq": "^1.6.0"
+ }
+ },
+ "acorn": {
+ "version": "8.8.1",
+ "resolved": "https://registry.npmjs.org/acorn/-/acorn-8.8.1.tgz",
+ "integrity": "sha512-7zFpHzhnqYKrkYdUjF1HI1bzd0VygEGX8lFk4k5zVMqHEoES+P+7TKI+EvLO9WVMJ8eekdO0aDEK044xTXwPPA=="
+ },
+ "acorn-jsx": {
+ "version": "5.3.2",
+ "resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-5.3.2.tgz",
+ "integrity": "sha512-rq9s+JNhf0IChjtDXxllJ7g41oZk5SlXtp0LHwyA5cejwn7vKmKp4pPri6YEePv2PU65sAsegbXtIinmDFDXgQ==",
+ "requires": {}
+ },
+ "ajv": {
+ "version": "6.12.6",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-6.12.6.tgz",
+ "integrity": "sha512-j3fVLgvTo527anyYyJOGTYJbG+vnnQYvE0m5mmkc1TK+nxAppkCLMIL0aZ4dblVCNoGShhm+kzE4ZUykBoMg4g==",
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "fast-json-stable-stringify": "^2.0.0",
+ "json-schema-traverse": "^0.4.1",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "ansi-colors": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/ansi-colors/-/ansi-colors-4.1.1.tgz",
+ "integrity": "sha512-JoX0apGbHaUJBNl6yF+p6JAFYZ666/hhCGKN5t9QFjbJQKUU/g8MNbFDbvfrgKXvI1QpZplPOnwIo99lX/AAmA=="
+ },
+ "ansi-regex": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/ansi-regex/-/ansi-regex-5.0.1.tgz",
+ "integrity": "sha512-quJQXlTSUGL2LH9SUXo8VwsY4soanhgo6LNSm84E1LBcE8s3O0wpdiRzyR9z/ZZJMlMWv37qOOb9pdJlMUEKFQ=="
+ },
+ "ansi-styles": {
+ "version": "3.2.1",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-3.2.1.tgz",
+ "integrity": "sha512-VT0ZI6kZRdTh8YyJw3SMbYm/u+NqfsAxEpWO0Pf9sq8/e94WxxOpPKx9FR1FlyCtOVDNOQ+8ntlqFxiRc+r5qA==",
+ "requires": {
+ "color-convert": "^1.9.0"
+ }
+ },
+ "anymatch": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/anymatch/-/anymatch-3.1.3.tgz",
+ "integrity": "sha512-KMReFUr0B4t+D+OBkjR3KYqvocp2XaSzO55UcB6mgQMd3KbcE+mWTyvVV7D/zsdEbNnV6acZUutkiHQXvTr1Rw==",
+ "dev": true,
+ "requires": {
+ "normalize-path": "^3.0.0",
+ "picomatch": "^2.0.4"
+ }
+ },
+ "argparse": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/argparse/-/argparse-2.0.1.tgz",
+ "integrity": "sha512-8+9WqebbFzpX9OR+Wa6O29asIogeRMzcGtAINdpMHHyAg10f05aSFVBbcEqGf/PXw1EjAZ+q2/bEBg3DvurK3Q=="
+ },
+ "array-includes": {
+ "version": "3.1.6",
+ "resolved": "https://registry.npmjs.org/array-includes/-/array-includes-3.1.6.tgz",
+ "integrity": "sha512-sgTbLvL6cNnw24FnbaDyjmvddQ2ML8arZsgaJhoABMoplz/4QRhtrYS+alr1BUM1Bwp6dhx8vVCBSLG+StwOFw==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "is-string": "^1.0.7"
+ }
+ },
+ "array.prototype.flatmap": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/array.prototype.flatmap/-/array.prototype.flatmap-1.3.1.tgz",
+ "integrity": "sha512-8UGn9O1FDVvMNB0UlLv4voxRMze7+FpHyF5mSMRjWHUMlpoDViniy05870VlxhfgTnLbpuwTzvD76MTtWxB/mQ==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "es-shim-unscopables": "^1.0.0"
+ }
+ },
+ "astral-regex": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/astral-regex/-/astral-regex-2.0.0.tgz",
+ "integrity": "sha512-Z7tMw1ytTXt5jqMcOP+OQteU1VuNK9Y02uuJtKQ1Sv69jXQKKg5cibLwGJow8yzZP+eAc18EmLGPal0bp36rvQ==",
+ "peer": true
+ },
+ "balanced-match": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/balanced-match/-/balanced-match-1.0.2.tgz",
+ "integrity": "sha512-3oSeUO0TMV67hN1AmbXsK4yaqU7tjiHlbxRDZOpH0KW9+CeX4bRAaX0Anxt0tx2MrpRpWwQaPwIlISEJhYU5Pw=="
+ },
+ "binary-extensions": {
+ "version": "2.2.0",
+ "resolved": "https://registry.npmjs.org/binary-extensions/-/binary-extensions-2.2.0.tgz",
+ "integrity": "sha512-jDctJ/IVQbZoJykoeHbhXpOlNBqGNcwXJKJog42E5HDPUwQTSdjCHdihjj0DlnheQ7blbT6dHOafNAiS8ooQKA==",
+ "dev": true
+ },
+ "brace-expansion": {
+ "version": "1.1.11",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-1.1.11.tgz",
+ "integrity": "sha512-iCuPHDFgrHX7H2vEI/5xpz07zSHB00TpugqhmYtVmMO6518mCuRMoOYFldEBl0g187ufozdaHgWKcYFb61qGiA==",
+ "requires": {
+ "balanced-match": "^1.0.0",
+ "concat-map": "0.0.1"
+ }
+ },
+ "braces": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/braces/-/braces-3.0.2.tgz",
+ "integrity": "sha512-b8um+L1RzM3WDSzvhm6gIz1yfTbBt6YTlcEKAvsmqCZZFw46z626lVj9j1yEPW33H5H+lBQpZMP1k8l+78Ha0A==",
+ "dev": true,
+ "requires": {
+ "fill-range": "^7.0.1"
+ }
+ },
+ "browser-stdout": {
+ "version": "1.3.1",
+ "resolved": "https://registry.npmjs.org/browser-stdout/-/browser-stdout-1.3.1.tgz",
+ "integrity": "sha512-qhAVI1+Av2X7qelOfAIYwXONood6XlZE/fXaBSmW/T5SzLAmCgzi+eiWE7fUvbHaeNBQH13UftjpXxsfLkMpgw==",
+ "dev": true
+ },
+ "browserslist": {
+ "version": "4.21.4",
+ "resolved": "https://registry.npmjs.org/browserslist/-/browserslist-4.21.4.tgz",
+ "integrity": "sha512-CBHJJdDmgjl3daYjN5Cp5kbTf1mUhZoS+beLklHIvkOWscs83YAhLlF3Wsh/lciQYAcbBJgTOD44VtG31ZM4Hw==",
+ "requires": {
+ "caniuse-lite": "^1.0.30001400",
+ "electron-to-chromium": "^1.4.251",
+ "node-releases": "^2.0.6",
+ "update-browserslist-db": "^1.0.9"
+ }
+ },
+ "call-bind": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/call-bind/-/call-bind-1.0.2.tgz",
+ "integrity": "sha512-7O+FbCihrB5WGbFYesctwmTKae6rOiIzmz1icreWJ+0aA7LJfuqhEso2T9ncpcFtzMQtzXf2QGGueWJGTYsqrA==",
+ "peer": true,
+ "requires": {
+ "function-bind": "^1.1.1",
+ "get-intrinsic": "^1.0.2"
+ }
+ },
+ "callsites": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/callsites/-/callsites-3.1.0.tgz",
+ "integrity": "sha512-P8BjAsXvZS+VIDUI11hHCQEv74YT67YUi5JJFNWIqL235sBmjX4+qx9Muvls5ivyNENctx46xQLQ3aTuE7ssaQ=="
+ },
+ "camelcase": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/camelcase/-/camelcase-6.3.0.tgz",
+ "integrity": "sha512-Gmy6FhYlCY7uOElZUSbxo2UCDH8owEk996gkbrpsgGtrJLM3J7jGxl9Ic7Qwwj4ivOE5AWZWRMecDdF7hqGjFA==",
+ "dev": true
+ },
+ "caniuse-lite": {
+ "version": "1.0.30001436",
+ "resolved": "https://registry.npmjs.org/caniuse-lite/-/caniuse-lite-1.0.30001436.tgz",
+ "integrity": "sha512-ZmWkKsnC2ifEPoWUvSAIGyOYwT+keAaaWPHiQ9DfMqS1t6tfuyFYoWR78TeZtznkEQ64+vGXH9cZrElwR2Mrxg=="
+ },
+ "chalk": {
+ "version": "2.4.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-2.4.2.tgz",
+ "integrity": "sha512-Mti+f9lpJNcwF4tWV8/OrTTtF1gZi+f8FqlyAdouralcFWFQWF2+NgCHShjkCb+IFBLq9buZwE1xckQU4peSuQ==",
+ "requires": {
+ "ansi-styles": "^3.2.1",
+ "escape-string-regexp": "^1.0.5",
+ "supports-color": "^5.3.0"
+ }
+ },
+ "chokidar": {
+ "version": "3.5.3",
+ "resolved": "https://registry.npmjs.org/chokidar/-/chokidar-3.5.3.tgz",
+ "integrity": "sha512-Dr3sfKRP6oTcjf2JmUmFJfeVMvXBdegxB0iVQ5eb2V10uFJUCAS8OByZdVAyVb8xXNz3GjjTgj9kLWsZTqE6kw==",
+ "dev": true,
+ "requires": {
+ "anymatch": "~3.1.2",
+ "braces": "~3.0.2",
+ "fsevents": "~2.3.2",
+ "glob-parent": "~5.1.2",
+ "is-binary-path": "~2.1.0",
+ "is-glob": "~4.0.1",
+ "normalize-path": "~3.0.0",
+ "readdirp": "~3.6.0"
+ },
+ "dependencies": {
+ "glob-parent": {
+ "version": "5.1.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-5.1.2.tgz",
+ "integrity": "sha512-AOIgSQCepiJYwP3ARnGx+5VnTu2HBYdzbGP45eLw1vr3zB3vZLeyed1sC9hnbcOc9/SrMyM5RPQrkGz4aS9Zow==",
+ "dev": true,
+ "requires": {
+ "is-glob": "^4.0.1"
+ }
+ }
+ }
+ },
+ "cliui": {
+ "version": "7.0.4",
+ "resolved": "https://registry.npmjs.org/cliui/-/cliui-7.0.4.tgz",
+ "integrity": "sha512-OcRE68cOsVMXp1Yvonl/fzkQOyjLSu/8bhPDfQt0e0/Eb283TKP20Fs2MqoPsr9SwA595rRCA+QMzYc9nBP+JQ==",
+ "dev": true,
+ "requires": {
+ "string-width": "^4.2.0",
+ "strip-ansi": "^6.0.0",
+ "wrap-ansi": "^7.0.0"
+ }
+ },
+ "color-convert": {
+ "version": "1.9.3",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-1.9.3.tgz",
+ "integrity": "sha512-QfAUtd+vFdAtFQcC8CCyYt1fYWxSqAiK2cSD6zDB8N3cpsEBAvRxp9zOGg6G/SHHJYAT88/az/IuDGALsNVbGg==",
+ "requires": {
+ "color-name": "1.1.3"
+ }
+ },
+ "color-name": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.3.tgz",
+ "integrity": "sha512-72fSenhMw2HZMTVHeCA9KCmpEIbzWiQsjN+BHcBbS9vr1mtt+vJjPdksIBNUmKAW8TFUDPJK5SUU3QhE9NEXDw=="
+ },
+ "concat-map": {
+ "version": "0.0.1",
+ "resolved": "https://registry.npmjs.org/concat-map/-/concat-map-0.0.1.tgz",
+ "integrity": "sha512-/Srv4dswyQNBfohGpz9o6Yb3Gz3SrUDqBH5rTuhGR7ahtlbYKnVxw2bCFMRljaA7EXHaXZ8wsHdodFvbkhKmqg=="
+ },
+ "convert-source-map": {
+ "version": "1.9.0",
+ "resolved": "https://registry.npmjs.org/convert-source-map/-/convert-source-map-1.9.0.tgz",
+ "integrity": "sha512-ASFBup0Mz1uyiIjANan1jzLQami9z1PoYSZCiiYW2FczPbenXc45FZdBZLzOT+r6+iciuEModtmCti+hjaAk0A=="
+ },
+ "cross-spawn": {
+ "version": "7.0.3",
+ "resolved": "https://registry.npmjs.org/cross-spawn/-/cross-spawn-7.0.3.tgz",
+ "integrity": "sha512-iRDPJKUPVEND7dHPO8rkbOnPpyDygcDFtWjpeWNCgy8WP2rXcxXL8TskReQl6OrB2G7+UJrags1q15Fudc7G6w==",
+ "requires": {
+ "path-key": "^3.1.0",
+ "shebang-command": "^2.0.0",
+ "which": "^2.0.1"
+ }
+ },
+ "debug": {
+ "version": "4.3.4",
+ "resolved": "https://registry.npmjs.org/debug/-/debug-4.3.4.tgz",
+ "integrity": "sha512-PRWFHuSU3eDtQJPvnNY7Jcket1j0t5OuOsFzPPzsekD52Zl8qUfFIPEiswXqIvHWGVHOgX+7G/vCNNhehwxfkQ==",
+ "requires": {
+ "ms": "2.1.2"
+ }
+ },
+ "decamelize": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/decamelize/-/decamelize-4.0.0.tgz",
+ "integrity": "sha512-9iE1PgSik9HeIIw2JO94IidnE3eBoQrFJ3w7sFuzSX4DpmZ3v5sZpUiV5Swcf6mQEF+Y0ru8Neo+p+nyh2J+hQ==",
+ "dev": true
+ },
+ "deep-is": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/deep-is/-/deep-is-0.1.4.tgz",
+ "integrity": "sha512-oIPzksmTg4/MriiaYGO+okXDT7ztn/w3Eptv/+gSIdMdKsJo0u4CfYNFJPy+4SKMuCqGw2wxnA+URMg3t8a/bQ=="
+ },
+ "define-properties": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/define-properties/-/define-properties-1.1.4.tgz",
+ "integrity": "sha512-uckOqKcfaVvtBdsVkdPv3XjveQJsNQqmhXgRi8uhvWWuPYZCNlzT8qAyblUgNoXdHdjMTzAqeGjAoli8f+bzPA==",
+ "peer": true,
+ "requires": {
+ "has-property-descriptors": "^1.0.0",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "diff": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/diff/-/diff-5.0.0.tgz",
+ "integrity": "sha512-/VTCrvm5Z0JGty/BWHljh+BAiw3IK+2j87NGMu8Nwc/f48WoDAC395uomO9ZD117ZOBaHmkX1oyLvkVM/aIT3w==",
+ "dev": true
+ },
+ "doctrine": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/doctrine/-/doctrine-3.0.0.tgz",
+ "integrity": "sha512-yS+Q5i3hBf7GBkd4KG8a7eBNNWNGLTaEwwYWUijIYM7zrlYDM0BFXHjjPWlWZ1Rg7UaddZeIDmi9jF3HmqiQ2w==",
+ "requires": {
+ "esutils": "^2.0.2"
+ }
+ },
+ "dom-serializer": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/dom-serializer/-/dom-serializer-2.0.0.tgz",
+ "integrity": "sha512-wIkAryiqt/nV5EQKqQpo3SToSOV9J0DnbJqwK7Wv/Trc92zIAYZ4FlMu+JPFW1DfGFt81ZTCGgDEabffXeLyJg==",
+ "requires": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "entities": "^4.2.0"
+ }
+ },
+ "domelementtype": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/domelementtype/-/domelementtype-2.3.0.tgz",
+ "integrity": "sha512-OLETBj6w0OsagBwdXnPdN0cnMfF9opN69co+7ZrbfPGrdpPVNBUj02spi6B1N7wChLQiPn4CSH/zJvXw56gmHw=="
+ },
+ "domhandler": {
+ "version": "5.0.3",
+ "resolved": "https://registry.npmjs.org/domhandler/-/domhandler-5.0.3.tgz",
+ "integrity": "sha512-cgwlv/1iFQiFnU96XXgROh8xTeetsnJiDsTc7TYCLFd9+/WNkIqPTxiM/8pSd8VIrhXGTf1Ny1q1hquVqDJB5w==",
+ "requires": {
+ "domelementtype": "^2.3.0"
+ }
+ },
+ "domutils": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/domutils/-/domutils-3.0.1.tgz",
+ "integrity": "sha512-z08c1l761iKhDFtfXO04C7kTdPBLi41zwOZl00WS8b5eiaebNpY00HKbztwBq+e3vyqWNwWF3mP9YLUeqIrF+Q==",
+ "requires": {
+ "dom-serializer": "^2.0.0",
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.1"
+ }
+ },
+ "electron-to-chromium": {
+ "version": "1.4.284",
+ "resolved": "https://registry.npmjs.org/electron-to-chromium/-/electron-to-chromium-1.4.284.tgz",
+ "integrity": "sha512-M8WEXFuKXMYMVr45fo8mq0wUrrJHheiKZf6BArTKk9ZBYCKJEOU5H8cdWgDT+qCVZf7Na4lVUaZsA+h6uA9+PA=="
+ },
+ "emoji-regex": {
+ "version": "8.0.0",
+ "resolved": "https://registry.npmjs.org/emoji-regex/-/emoji-regex-8.0.0.tgz",
+ "integrity": "sha512-MSjYzcWNOA0ewAHpz0MxpYFvwg6yjy1NG3xteoqz644VCo/RPgnr1/GGt+ic3iJTzQ8Eu3TdM14SawnVUmGE6A=="
+ },
+ "enquirer": {
+ "version": "2.3.6",
+ "resolved": "https://registry.npmjs.org/enquirer/-/enquirer-2.3.6.tgz",
+ "integrity": "sha512-yjNnPr315/FjS4zIsUxYguYUPP2e1NK4d7E7ZOLiyYCcbFBiTMyID+2wvm2w6+pZ/odMA7cRkjhsPbltwBOrLg==",
+ "peer": true,
+ "requires": {
+ "ansi-colors": "^4.1.1"
+ }
+ },
+ "entities": {
+ "version": "4.4.0",
+ "resolved": "https://registry.npmjs.org/entities/-/entities-4.4.0.tgz",
+ "integrity": "sha512-oYp7156SP8LkeGD0GF85ad1X9Ai79WtRsZ2gxJqtBuzH+98YUV6jkHEKlZkMbcrjJjIVJNIDP/3WL9wQkoPbWA=="
+ },
+ "es-abstract": {
+ "version": "1.20.4",
+ "resolved": "https://registry.npmjs.org/es-abstract/-/es-abstract-1.20.4.tgz",
+ "integrity": "sha512-0UtvRN79eMe2L+UNEF1BwRe364sj/DXhQ/k5FmivgoSdpM90b8Jc0mDzKMGo7QS0BVbOP/bTwBKNnDc9rNzaPA==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "es-to-primitive": "^1.2.1",
+ "function-bind": "^1.1.1",
+ "function.prototype.name": "^1.1.5",
+ "get-intrinsic": "^1.1.3",
+ "get-symbol-description": "^1.0.0",
+ "has": "^1.0.3",
+ "has-property-descriptors": "^1.0.0",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "is-callable": "^1.2.7",
+ "is-negative-zero": "^2.0.2",
+ "is-regex": "^1.1.4",
+ "is-shared-array-buffer": "^1.0.2",
+ "is-string": "^1.0.7",
+ "is-weakref": "^1.0.2",
+ "object-inspect": "^1.12.2",
+ "object-keys": "^1.1.1",
+ "object.assign": "^4.1.4",
+ "regexp.prototype.flags": "^1.4.3",
+ "safe-regex-test": "^1.0.0",
+ "string.prototype.trimend": "^1.0.5",
+ "string.prototype.trimstart": "^1.0.5",
+ "unbox-primitive": "^1.0.2"
+ }
+ },
+ "es-shim-unscopables": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/es-shim-unscopables/-/es-shim-unscopables-1.0.0.tgz",
+ "integrity": "sha512-Jm6GPcCdC30eMLbZ2x8z2WuRwAws3zTBBKuusffYVUrNj/GVSUAZ+xKMaUpfNDR5IbyNA5LJbaecoUVbmUcB1w==",
+ "peer": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "es-to-primitive": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/es-to-primitive/-/es-to-primitive-1.2.1.tgz",
+ "integrity": "sha512-QCOllgZJtaUo9miYBcLChTUaHNjJF3PYs1VidD7AwiEj1kYxKeQTctLAezAOH5ZKRH0g2IgPn6KwB4IT8iRpvA==",
+ "peer": true,
+ "requires": {
+ "is-callable": "^1.1.4",
+ "is-date-object": "^1.0.1",
+ "is-symbol": "^1.0.2"
+ }
+ },
+ "escalade": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/escalade/-/escalade-3.1.1.tgz",
+ "integrity": "sha512-k0er2gUkLf8O0zKJiAhmkTnJlTvINGv7ygDNPbeIsX/TJjGJZHuh9B2UxbsaEkmlEo9MfhrSzmhIlhRlI2GXnw=="
+ },
+ "escape-string-regexp": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-1.0.5.tgz",
+ "integrity": "sha512-vbRorB5FUQWvla16U8R/qgaFIya2qGzwDrNmCZuYKrbdSUMG6I1ZCGQRefkRVhuOkIGVne7BQ35DSfo1qvJqFg=="
+ },
+ "eslint": {
+ "version": "8.29.0",
+ "resolved": "https://registry.npmjs.org/eslint/-/eslint-8.29.0.tgz",
+ "integrity": "sha512-isQ4EEiyUjZFbEKvEGJKKGBwXtvXX+zJbkVKCgTuB9t/+jUBcy8avhkEwWJecI15BkRkOYmvIM5ynbhRjEkoeg==",
+ "requires": {
+ "@eslint/eslintrc": "^1.3.3",
+ "@humanwhocodes/config-array": "^0.11.6",
+ "@humanwhocodes/module-importer": "^1.0.1",
+ "@nodelib/fs.walk": "^1.2.8",
+ "ajv": "^6.10.0",
+ "chalk": "^4.0.0",
+ "cross-spawn": "^7.0.2",
+ "debug": "^4.3.2",
+ "doctrine": "^3.0.0",
+ "escape-string-regexp": "^4.0.0",
+ "eslint-scope": "^7.1.1",
+ "eslint-utils": "^3.0.0",
+ "eslint-visitor-keys": "^3.3.0",
+ "espree": "^9.4.0",
+ "esquery": "^1.4.0",
+ "esutils": "^2.0.2",
+ "fast-deep-equal": "^3.1.3",
+ "file-entry-cache": "^6.0.1",
+ "find-up": "^5.0.0",
+ "glob-parent": "^6.0.2",
+ "globals": "^13.15.0",
+ "grapheme-splitter": "^1.0.4",
+ "ignore": "^5.2.0",
+ "import-fresh": "^3.0.0",
+ "imurmurhash": "^0.1.4",
+ "is-glob": "^4.0.0",
+ "is-path-inside": "^3.0.3",
+ "js-sdsl": "^4.1.4",
+ "js-yaml": "^4.1.0",
+ "json-stable-stringify-without-jsonify": "^1.0.1",
+ "levn": "^0.4.1",
+ "lodash.merge": "^4.6.2",
+ "minimatch": "^3.1.2",
+ "natural-compare": "^1.4.0",
+ "optionator": "^0.9.1",
+ "regexpp": "^3.2.0",
+ "strip-ansi": "^6.0.1",
+ "strip-json-comments": "^3.1.0",
+ "text-table": "^0.2.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA=="
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA=="
+ },
+ "globals": {
+ "version": "13.18.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-13.18.0.tgz",
+ "integrity": "sha512-/mR4KI8Ps2spmoc0Ulu9L7agOF0du1CZNQ3dke8yItYlyKNmGrkONemBbd6V8UTc1Wgcqn21t3WYB7dbRmh6/A==",
+ "requires": {
+ "type-fest": "^0.20.2"
+ }
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ=="
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "eslint-config-prettier": {
+ "version": "8.5.0",
+ "resolved": "https://registry.npmjs.org/eslint-config-prettier/-/eslint-config-prettier-8.5.0.tgz",
+ "integrity": "sha512-obmWKLUNCnhtQRKc+tmnYuQl0pFU1ibYJQ5BGhTVB08bHe9wC8qUeG7c08dj9XX+AuPj1YSGSQIHl1pnDHZR0Q==",
+ "peer": true,
+ "requires": {}
+ },
+ "eslint-plugin-es": {
+ "version": "3.0.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-es/-/eslint-plugin-es-3.0.1.tgz",
+ "integrity": "sha512-GUmAsJaN4Fc7Gbtl8uOBlayo2DqhwWvEzykMHSCZHU3XdJ+NSzzZcVhXh3VxX5icqQ+oQdIEawXX8xkR3mIFmQ==",
+ "peer": true,
+ "requires": {
+ "eslint-utils": "^2.0.0",
+ "regexpp": "^3.0.0"
+ },
+ "dependencies": {
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "peer": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true
+ }
+ }
+ },
+ "eslint-plugin-fetch-options": {
+ "version": "0.0.5",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-fetch-options/-/eslint-plugin-fetch-options-0.0.5.tgz",
+ "integrity": "sha512-ZMxrccsOAZ7uMQ4nMvPJLqLg6oyLF96YOEwTKWAIbDHpwWUp1raXALZom8ikKucaEnhqWSRuBWI8jBXveFwkJg==",
+ "peer": true
+ },
+ "eslint-plugin-html": {
+ "version": "7.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-html/-/eslint-plugin-html-7.1.0.tgz",
+ "integrity": "sha512-fNLRraV/e6j8e3XYOC9xgND4j+U7b1Rq+OygMlLcMg+wI/IpVbF+ubQa3R78EjKB9njT6TQOlcK5rFKBVVtdfg==",
+ "peer": true,
+ "requires": {
+ "htmlparser2": "^8.0.1"
+ }
+ },
+ "eslint-plugin-no-unsanitized": {
+ "version": "4.0.2",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-no-unsanitized/-/eslint-plugin-no-unsanitized-4.0.2.tgz",
+ "integrity": "sha512-Pry0S9YmHoz8NCEMRQh7N0Yexh2MYCNPIlrV52hTmS7qXnTghWsjXouF08bgsrrZqaW9tt1ZiK3j5NEmPE+EjQ==",
+ "peer": true,
+ "requires": {}
+ },
+ "eslint-plugin-node": {
+ "version": "11.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-node/-/eslint-plugin-node-11.1.0.tgz",
+ "integrity": "sha512-oUwtPJ1W0SKD0Tr+wqu92c5xuCeQqB3hSCHasn/ZgjFdA9iDGNkNf2Zi9ztY7X+hNuMib23LNGRm6+uN+KLE3g==",
+ "peer": true,
+ "requires": {
+ "eslint-plugin-es": "^3.0.0",
+ "eslint-utils": "^2.0.0",
+ "ignore": "^5.1.1",
+ "minimatch": "^3.0.4",
+ "resolve": "^1.10.1",
+ "semver": "^6.1.0"
+ },
+ "dependencies": {
+ "eslint-utils": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-2.1.0.tgz",
+ "integrity": "sha512-w94dQYoauyvlDc43XnGB8lU3Zt713vNChgt4EWwhXAP2XkBvndfxF0AgIqKOOasjPIPzj9JqgwkwbCYD0/V3Zg==",
+ "peer": true,
+ "requires": {
+ "eslint-visitor-keys": "^1.1.0"
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "1.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-1.3.0.tgz",
+ "integrity": "sha512-6J72N8UNa462wa/KFODt/PJ3IU60SDpC3QXC1Hjc1BXXpfL2C9R5+AU7jhe0F6GREqVMh4Juu+NY7xn+6dipUQ==",
+ "peer": true
+ }
+ }
+ },
+ "eslint-plugin-prettier": {
+ "version": "3.4.1",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-prettier/-/eslint-plugin-prettier-3.4.1.tgz",
+ "integrity": "sha512-htg25EUYUeIhKHXjOinK4BgCcDwtLHjqaxCDsMy5nbnUMkKFvIhMVCp+5GFUXQ4Nr8lBsPqtGAqBenbpFqAA2g==",
+ "peer": true,
+ "requires": {
+ "prettier-linter-helpers": "^1.0.0"
+ }
+ },
+ "eslint-plugin-security": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/eslint-plugin-security/-/eslint-plugin-security-1.4.0.tgz",
+ "integrity": "sha512-xlS7P2PLMXeqfhyf3NpqbvbnW04kN8M9NtmhpR3XGyOvt/vNKS7XPXT5EDbwKW9vCjWH4PpfQvgD/+JgN0VJKA==",
+ "peer": true,
+ "requires": {
+ "safe-regex": "^1.1.0"
+ }
+ },
+ "eslint-scope": {
+ "version": "7.1.1",
+ "resolved": "https://registry.npmjs.org/eslint-scope/-/eslint-scope-7.1.1.tgz",
+ "integrity": "sha512-QKQM/UXpIiHcLqJ5AOyIW7XZmzjkzQXYE54n1++wb0u9V/abW3l9uQnxX8Z5Xd18xyKIMTUAyQ0k1e8pz6LUrw==",
+ "requires": {
+ "esrecurse": "^4.3.0",
+ "estraverse": "^5.2.0"
+ }
+ },
+ "eslint-utils": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/eslint-utils/-/eslint-utils-3.0.0.tgz",
+ "integrity": "sha512-uuQC43IGctw68pJA1RgbQS8/NP7rch6Cwd4j3ZBtgo4/8Flj4eGE7ZYSZRN3iq5pVUv6GPdW5Z1RFleo84uLDA==",
+ "requires": {
+ "eslint-visitor-keys": "^2.0.0"
+ },
+ "dependencies": {
+ "eslint-visitor-keys": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-2.1.0.tgz",
+ "integrity": "sha512-0rSmRBzXgDzIsD6mGdJgevzgezI534Cer5L/vyMX0kHzT/jiB43jRhd9YUlMGYLQy2zprNmoT8qasCGtY+QaKw=="
+ }
+ }
+ },
+ "eslint-visitor-keys": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/eslint-visitor-keys/-/eslint-visitor-keys-3.3.0.tgz",
+ "integrity": "sha512-mQ+suqKJVyeuwGYHAdjMFqjCyfl8+Ldnxuyp3ldiMBFKkvytrXUZWaiPCEav8qDHKty44bD+qV1IP4T+w+xXRA=="
+ },
+ "espree": {
+ "version": "9.4.1",
+ "resolved": "https://registry.npmjs.org/espree/-/espree-9.4.1.tgz",
+ "integrity": "sha512-XwctdmTO6SIvCzd9810yyNzIrOrqNYV9Koizx4C/mRhf9uq0o4yHoCEU/670pOxOL/MSraektvSAji79kX90Vg==",
+ "requires": {
+ "acorn": "^8.8.0",
+ "acorn-jsx": "^5.3.2",
+ "eslint-visitor-keys": "^3.3.0"
+ }
+ },
+ "esprima": {
+ "version": "4.0.1",
+ "resolved": "https://registry.npmjs.org/esprima/-/esprima-4.0.1.tgz",
+ "integrity": "sha512-eGuFFw7Upda+g4p+QHvnW0RyTX/SVeJBDM/gCtMARO0cLuT2HcEKnTPvhjV6aGeqrCB/sbNop0Kszm0jsaWU4A==",
+ "peer": true
+ },
+ "esquery": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/esquery/-/esquery-1.4.0.tgz",
+ "integrity": "sha512-cCDispWt5vHHtwMY2YrAQ4ibFkAL8RbH5YGBnZBc90MolvvfkkQcJro/aZiAQUlQ3qgrYS6D6v8Gc5G5CQsc9w==",
+ "requires": {
+ "estraverse": "^5.1.0"
+ }
+ },
+ "esrecurse": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/esrecurse/-/esrecurse-4.3.0.tgz",
+ "integrity": "sha512-KmfKL3b6G+RXvP8N1vr3Tq1kL/oCFgn2NYXEtqP8/L3pKapUA4G8cFVaoF3SU323CD4XypR/ffioHmkti6/Tag==",
+ "requires": {
+ "estraverse": "^5.2.0"
+ }
+ },
+ "estraverse": {
+ "version": "5.3.0",
+ "resolved": "https://registry.npmjs.org/estraverse/-/estraverse-5.3.0.tgz",
+ "integrity": "sha512-MMdARuVEQziNTeJD8DgMqmhwR11BRQ/cBP+pLtYdSTnf3MIO8fFeiINEbX36ZdNlfU/7A9f3gUw49B3oQsvwBA=="
+ },
+ "esutils": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/esutils/-/esutils-2.0.3.tgz",
+ "integrity": "sha512-kVscqXk4OCp68SZ0dkgEKVi6/8ij300KBWTJq32P/dYeWTSwK41WyTxalN1eRmA5Z9UU/LX9D7FWSmV9SAYx6g=="
+ },
+ "fast-deep-equal": {
+ "version": "3.1.3",
+ "resolved": "https://registry.npmjs.org/fast-deep-equal/-/fast-deep-equal-3.1.3.tgz",
+ "integrity": "sha512-f3qQ9oQy9j2AhBe/H9VC91wLmKBCCU/gDOnKNAYG5hswO7BLKj09Hc5HYNz9cGI++xlpDCIgDaitVs03ATR84Q=="
+ },
+ "fast-diff": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/fast-diff/-/fast-diff-1.2.0.tgz",
+ "integrity": "sha512-xJuoT5+L99XlZ8twedaRf6Ax2TgQVxvgZOYoPKqZufmJib0tL2tegPBOZb1pVNgIhlqDlA0eO0c3wBvQcmzx4w==",
+ "peer": true
+ },
+ "fast-json-stable-stringify": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/fast-json-stable-stringify/-/fast-json-stable-stringify-2.1.0.tgz",
+ "integrity": "sha512-lhd/wF+Lk98HZoTCtlVraHtfh5XYijIjalXck7saUtuanSDyLMxnHhSXEDJqHxD7msR8D0uCmqlkwjCV8xvwHw=="
+ },
+ "fast-levenshtein": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/fast-levenshtein/-/fast-levenshtein-2.0.6.tgz",
+ "integrity": "sha512-DCXu6Ifhqcks7TZKY3Hxp3y6qphY5SJZmrWMDrKcERSOXWQdMhU9Ig/PYrzyw/ul9jOIyh0N4M0tbC5hodg8dw=="
+ },
+ "fastq": {
+ "version": "1.14.0",
+ "resolved": "https://registry.npmjs.org/fastq/-/fastq-1.14.0.tgz",
+ "integrity": "sha512-eR2D+V9/ExcbF9ls441yIuN6TI2ED1Y2ZcA5BmMtJsOkWOFRJQ0Jt0g1UwqXJJVAb+V+umH5Dfr8oh4EVP7VVg==",
+ "requires": {
+ "reusify": "^1.0.4"
+ }
+ },
+ "file-entry-cache": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/file-entry-cache/-/file-entry-cache-6.0.1.tgz",
+ "integrity": "sha512-7Gps/XWymbLk2QLYK4NzpMOrYjMhdIxXuIvy2QBsLE6ljuodKvdkWs/cpyJJ3CVIVpH0Oi1Hvg1ovbMzLdFBBg==",
+ "requires": {
+ "flat-cache": "^3.0.4"
+ }
+ },
+ "fill-range": {
+ "version": "7.0.1",
+ "resolved": "https://registry.npmjs.org/fill-range/-/fill-range-7.0.1.tgz",
+ "integrity": "sha512-qOo9F+dMUmC2Lcb4BbVvnKJxTPjCm+RRpe4gDuGrzkL7mEVl/djYSu2OdQ2Pa302N4oqkSg9ir6jaLWJ2USVpQ==",
+ "dev": true,
+ "requires": {
+ "to-regex-range": "^5.0.1"
+ }
+ },
+ "find-up": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/find-up/-/find-up-5.0.0.tgz",
+ "integrity": "sha512-78/PXT1wlLLDgTzDs7sjq9hzz0vXD+zn+7wypEe4fXQxCmdmqfGsEPQxmiCSQI3ajFV91bVSsvNtrJRiW6nGng==",
+ "requires": {
+ "locate-path": "^6.0.0",
+ "path-exists": "^4.0.0"
+ }
+ },
+ "flat": {
+ "version": "5.0.2",
+ "resolved": "https://registry.npmjs.org/flat/-/flat-5.0.2.tgz",
+ "integrity": "sha512-b6suED+5/3rTpUBdG1gupIl8MPFCAMA0QXwmljLhvCUKcUvdE4gWky9zpuGCcXHOsz4J9wPGNWq6OKpmIzz3hQ==",
+ "dev": true
+ },
+ "flat-cache": {
+ "version": "3.0.4",
+ "resolved": "https://registry.npmjs.org/flat-cache/-/flat-cache-3.0.4.tgz",
+ "integrity": "sha512-dm9s5Pw7Jc0GvMYbshN6zchCA9RgQlzzEZX3vylR9IqFfS8XciblUXOKfW6SiuJ0e13eDYZoZV5wdrev7P3Nwg==",
+ "requires": {
+ "flatted": "^3.1.0",
+ "rimraf": "^3.0.2"
+ }
+ },
+ "flatted": {
+ "version": "3.2.7",
+ "resolved": "https://registry.npmjs.org/flatted/-/flatted-3.2.7.tgz",
+ "integrity": "sha512-5nqDSxl8nn5BSNxyR3n4I6eDmbolI6WT+QqR547RwxQapgjQBmtktdP+HTBb/a/zLsbzERTONyUB5pefh5TtjQ=="
+ },
+ "fs.realpath": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/fs.realpath/-/fs.realpath-1.0.0.tgz",
+ "integrity": "sha512-OO0pH2lK6a0hZnAdau5ItzHPI6pUlvI7jMVnxUQRtw4owF2wk8lOSabtGDCTP4Ggrg2MbGnWO9X8K1t4+fGMDw=="
+ },
+ "fsevents": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/fsevents/-/fsevents-2.3.2.tgz",
+ "integrity": "sha512-xiqMQR4xAeHTuB9uWm+fFRcIOgKBMiOBP+eXiyT7jsgVCq1bkVygt00oASowB7EdtpOHaaPgKt812P9ab+DDKA==",
+ "dev": true,
+ "optional": true
+ },
+ "function-bind": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/function-bind/-/function-bind-1.1.1.tgz",
+ "integrity": "sha512-yIovAzMX49sF8Yl58fSCWJ5svSLuaibPxXQJFLmBObTuCr0Mf1KiPopGM9NiFjiYBCbfaa2Fh6breQ6ANVTI0A==",
+ "peer": true
+ },
+ "function.prototype.name": {
+ "version": "1.1.5",
+ "resolved": "https://registry.npmjs.org/function.prototype.name/-/function.prototype.name-1.1.5.tgz",
+ "integrity": "sha512-uN7m/BzVKQnCUF/iW8jYea67v++2u7m5UgENbHRtdDVclOUP+FMPlCNdmk0h/ysGyo2tavMJEDqJAkJdRa1vMA==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "es-abstract": "^1.19.0",
+ "functions-have-names": "^1.2.2"
+ }
+ },
+ "functional-red-black-tree": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/functional-red-black-tree/-/functional-red-black-tree-1.0.1.tgz",
+ "integrity": "sha512-dsKNQNdj6xA3T+QlADDA7mOSlX0qiMINjn0cgr+eGHGsbSHzTabcIogz2+p/iqP1Xs6EP/sS2SbqH+brGTbq0g==",
+ "peer": true
+ },
+ "functions-have-names": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/functions-have-names/-/functions-have-names-1.2.3.tgz",
+ "integrity": "sha512-xckBUXyTIqT97tq2x2AMb+g163b5JFysYk0x4qxNFwbfQkmNZoiRHb6sPzI9/QV33WeuvVYBUIiD4NzNIyqaRQ==",
+ "peer": true
+ },
+ "gensync": {
+ "version": "1.0.0-beta.2",
+ "resolved": "https://registry.npmjs.org/gensync/-/gensync-1.0.0-beta.2.tgz",
+ "integrity": "sha512-3hN7NaskYvMDLQY55gnW3NQ+mesEAepTqlg+VEbj7zzqEMBVNhzcGYYeqFo/TlYz6eQiFcp1HcsCZO+nGgS8zg=="
+ },
+ "get-caller-file": {
+ "version": "2.0.5",
+ "resolved": "https://registry.npmjs.org/get-caller-file/-/get-caller-file-2.0.5.tgz",
+ "integrity": "sha512-DyFP3BM/3YHTQOCUL/w0OZHR0lpKeGrxotcHWcqNEdnltqFwXVfhEBQ94eIo34AfQpo0rGki4cyIiftY06h2Fg==",
+ "dev": true
+ },
+ "get-intrinsic": {
+ "version": "1.1.3",
+ "resolved": "https://registry.npmjs.org/get-intrinsic/-/get-intrinsic-1.1.3.tgz",
+ "integrity": "sha512-QJVz1Tj7MS099PevUG5jvnt9tSkXN8K14dxQlikJuPt4uD9hHAHjLyLBiLR5zELelBdD9QNRAXZzsJx0WaDL9A==",
+ "peer": true,
+ "requires": {
+ "function-bind": "^1.1.1",
+ "has": "^1.0.3",
+ "has-symbols": "^1.0.3"
+ }
+ },
+ "get-symbol-description": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/get-symbol-description/-/get-symbol-description-1.0.0.tgz",
+ "integrity": "sha512-2EmdH1YvIQiZpltCNgkuiUnyukzxM/R6NDJX31Ke3BG1Nq5b0S2PhX59UKi9vZpPDQVdqn+1IcaAwnzTT5vCjw==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.1"
+ }
+ },
+ "glob": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/glob/-/glob-7.2.0.tgz",
+ "integrity": "sha512-lmLf6gtyrPq8tTjSmrO94wBeQbFR3HbLHbuyD69wuyQkImp2hWqMGB47OX65FBkPffO641IP9jWa1z4ivqG26Q==",
+ "requires": {
+ "fs.realpath": "^1.0.0",
+ "inflight": "^1.0.4",
+ "inherits": "2",
+ "minimatch": "^3.0.4",
+ "once": "^1.3.0",
+ "path-is-absolute": "^1.0.0"
+ }
+ },
+ "glob-parent": {
+ "version": "6.0.2",
+ "resolved": "https://registry.npmjs.org/glob-parent/-/glob-parent-6.0.2.tgz",
+ "integrity": "sha512-XxwI8EOhVQgWp6iDL+3b0r86f4d6AX6zSU55HfB4ydCEuXLXc5FcYeOu+nnGftS4TEju/11rt4KJPTMgbfmv4A==",
+ "requires": {
+ "is-glob": "^4.0.3"
+ }
+ },
+ "globals": {
+ "version": "11.12.0",
+ "resolved": "https://registry.npmjs.org/globals/-/globals-11.12.0.tgz",
+ "integrity": "sha512-WOBp/EEGUiIsJSp7wcv/y6MO+lV9UoncWqxuFfm8eBwzWNgyfBd6Gz+IeKQ9jCmyhoH99g15M3T+QaVHFjizVA=="
+ },
+ "grapheme-splitter": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/grapheme-splitter/-/grapheme-splitter-1.0.4.tgz",
+ "integrity": "sha512-bzh50DW9kTPM00T8y4o8vQg89Di9oLJVLW/KaOGIXJWP/iqCN6WKYkbNOF04vFLJhwcpYUh9ydh/+5vpOqV4YQ=="
+ },
+ "has": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has/-/has-1.0.3.tgz",
+ "integrity": "sha512-f2dvO0VU6Oej7RkWJGrehjbzMAjFp5/VKPp5tTpWIV4JHHZK1/BxbFRtf/siA2SWTe09caDmVtYYzWEIbBS4zw==",
+ "peer": true,
+ "requires": {
+ "function-bind": "^1.1.1"
+ }
+ },
+ "has-bigints": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/has-bigints/-/has-bigints-1.0.2.tgz",
+ "integrity": "sha512-tSvCKtBr9lkF0Ex0aQiP9N+OpV4zi2r/Nee5VkRDbaqv35RLYMzbwQfFSZZH0kR+Rd6302UJZ2p/bJCEoR3VoQ==",
+ "peer": true
+ },
+ "has-flag": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-3.0.0.tgz",
+ "integrity": "sha512-sKJf1+ceQBr4SMkvQnBDNDtf4TXpVhVGateu0t918bl30FnbE2m4vNLX+VWe/dpjlb+HugGYzW7uQXH98HPEYw=="
+ },
+ "has-property-descriptors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-property-descriptors/-/has-property-descriptors-1.0.0.tgz",
+ "integrity": "sha512-62DVLZGoiEBDHQyqG4w9xCuZ7eJEwNmJRWw2VY84Oedb7WFcA27fiEVe8oUQx9hAUJ4ekurquucTGwsyO1XGdQ==",
+ "peer": true,
+ "requires": {
+ "get-intrinsic": "^1.1.1"
+ }
+ },
+ "has-symbols": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/has-symbols/-/has-symbols-1.0.3.tgz",
+ "integrity": "sha512-l3LCuF6MgDNwTDKkdYGEihYjt5pRPbEg46rtlmnSPlUbgmB8LOIrKJbYYFBSbnPaJexMKtiPO8hmeRjRz2Td+A==",
+ "peer": true
+ },
+ "has-tostringtag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/has-tostringtag/-/has-tostringtag-1.0.0.tgz",
+ "integrity": "sha512-kFjcSNhnlGV1kyoGk7OXKSawH5JOb/LzUc5w9B02hOTO0dfFRjbHQKvg1d6cf3HbeUmtU9VbbV3qzZ2Teh97WQ==",
+ "peer": true,
+ "requires": {
+ "has-symbols": "^1.0.2"
+ }
+ },
+ "he": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/he/-/he-1.2.0.tgz",
+ "integrity": "sha512-F/1DnUGPopORZi0ni+CvrCgHQ5FyEAHRLSApuYWMmrbSwoN2Mn/7k+Gl38gJnR7yyDZk6WLXwiGod1JOWNDKGw==",
+ "dev": true
+ },
+ "htmlparser2": {
+ "version": "8.0.1",
+ "resolved": "https://registry.npmjs.org/htmlparser2/-/htmlparser2-8.0.1.tgz",
+ "integrity": "sha512-4lVbmc1diZC7GUJQtRQ5yBAeUCL1exyMwmForWkRLnwyzWBFxN633SALPMGYaWZvKe9j1pRZJpauvmxENSp/EA==",
+ "requires": {
+ "domelementtype": "^2.3.0",
+ "domhandler": "^5.0.2",
+ "domutils": "^3.0.1",
+ "entities": "^4.3.0"
+ }
+ },
+ "ignore": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/ignore/-/ignore-5.2.1.tgz",
+ "integrity": "sha512-d2qQLzTJ9WxQftPAuEQpSPmKqzxePjzVbpAVv62AQ64NTL+wR4JkrVqR/LqFsFEUsHDAiId52mJteHDFuDkElA=="
+ },
+ "import-fresh": {
+ "version": "3.3.0",
+ "resolved": "https://registry.npmjs.org/import-fresh/-/import-fresh-3.3.0.tgz",
+ "integrity": "sha512-veYYhQa+D1QBKznvhUHxb8faxlrwUnxseDAbAp457E0wLNio2bOSKnjYDhMj+YiAq61xrMGhQk9iXVk5FzgQMw==",
+ "requires": {
+ "parent-module": "^1.0.0",
+ "resolve-from": "^4.0.0"
+ }
+ },
+ "imurmurhash": {
+ "version": "0.1.4",
+ "resolved": "https://registry.npmjs.org/imurmurhash/-/imurmurhash-0.1.4.tgz",
+ "integrity": "sha512-JmXMZ6wuvDmLiHEml9ykzqO6lwFbof0GG4IkcGaENdCRDDmMVnny7s5HsIgHCbaq0w2MyPhDqkhTUgS2LU2PHA=="
+ },
+ "inflight": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/inflight/-/inflight-1.0.6.tgz",
+ "integrity": "sha512-k92I/b08q4wvFscXCLvqfsHCrjrF7yiXsQuIVvVE7N82W3+aqpzuUdBbfhWcy/FZR3/4IgflMgKLOsvPDrGCJA==",
+ "requires": {
+ "once": "^1.3.0",
+ "wrappy": "1"
+ }
+ },
+ "inherits": {
+ "version": "2.0.4",
+ "resolved": "https://registry.npmjs.org/inherits/-/inherits-2.0.4.tgz",
+ "integrity": "sha512-k/vGaX4/Yla3WzyMCvTQOXYeIHvqOKtnqBduzTHpzpQZzAskKMhZ2K+EnBiSM9zGSoIFeMpXKxa4dYeZIQqewQ=="
+ },
+ "internal-slot": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/internal-slot/-/internal-slot-1.0.3.tgz",
+ "integrity": "sha512-O0DB1JC/sPyZl7cIo78n5dR7eUSwwpYPiXRhTzNxZVAMUuB8vlnRFyLxdrVToks6XPLVnFfbzaVd5WLjhgg+vA==",
+ "peer": true,
+ "requires": {
+ "get-intrinsic": "^1.1.0",
+ "has": "^1.0.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "is-bigint": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-bigint/-/is-bigint-1.0.4.tgz",
+ "integrity": "sha512-zB9CruMamjym81i2JZ3UMn54PKGsQzsJeo6xvN3HJJ4CAsQNB6iRutp2To77OfCNuoxspsIhzaPoO1zyCEhFOg==",
+ "peer": true,
+ "requires": {
+ "has-bigints": "^1.0.1"
+ }
+ },
+ "is-binary-path": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-binary-path/-/is-binary-path-2.1.0.tgz",
+ "integrity": "sha512-ZMERYes6pDydyuGidse7OsHxtbI7WVeUEozgR/g7rd0xUimYNlvZRE/K2MgZTjWy725IfelLeVcEM97mmtRGXw==",
+ "dev": true,
+ "requires": {
+ "binary-extensions": "^2.0.0"
+ }
+ },
+ "is-boolean-object": {
+ "version": "1.1.2",
+ "resolved": "https://registry.npmjs.org/is-boolean-object/-/is-boolean-object-1.1.2.tgz",
+ "integrity": "sha512-gDYaKHJmnj4aWxyj6YHyXVpdQawtVLHU5cb+eztPGczf6cjuTdwve5ZIEfgXqH4e57An1D1AKf8CZ3kYrQRqYA==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-callable": {
+ "version": "1.2.7",
+ "resolved": "https://registry.npmjs.org/is-callable/-/is-callable-1.2.7.tgz",
+ "integrity": "sha512-1BC0BVFhS/p0qtw6enp8e+8OD0UrK0oFLztSjNzhcKA3WDuJxxAPXzPuPtKkjEY9UUoEWlX/8fgKeu2S8i9JTA==",
+ "peer": true
+ },
+ "is-core-module": {
+ "version": "2.11.0",
+ "resolved": "https://registry.npmjs.org/is-core-module/-/is-core-module-2.11.0.tgz",
+ "integrity": "sha512-RRjxlvLDkD1YJwDbroBHMb+cukurkDWNyHx7D3oNB5x9rb5ogcksMC5wHCadcXoo67gVr/+3GFySh3134zi6rw==",
+ "peer": true,
+ "requires": {
+ "has": "^1.0.3"
+ }
+ },
+ "is-date-object": {
+ "version": "1.0.5",
+ "resolved": "https://registry.npmjs.org/is-date-object/-/is-date-object-1.0.5.tgz",
+ "integrity": "sha512-9YQaSxsAiSwcvS33MBk3wTCVnWK+HhF8VZR2jRxehM16QcVOdHqPn4VPHmRK4lSr38n9JriurInLcP90xsYNfQ==",
+ "peer": true,
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-extglob": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/is-extglob/-/is-extglob-2.1.1.tgz",
+ "integrity": "sha512-SbKbANkN603Vi4jEZv49LeVJMn4yGwsbzZworEoyEiutsN3nJYdbO36zfhGJ6QEDpOZIFkDtnq5JRxmvl3jsoQ=="
+ },
+ "is-fullwidth-code-point": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/is-fullwidth-code-point/-/is-fullwidth-code-point-3.0.0.tgz",
+ "integrity": "sha512-zymm5+u+sCsSWyD9qNaejV3DFvhCKclKdizYaJUuHA83RLjb7nSuGnddCHGv0hk+KY7BMAlsWeK4Ueg6EV6XQg=="
+ },
+ "is-glob": {
+ "version": "4.0.3",
+ "resolved": "https://registry.npmjs.org/is-glob/-/is-glob-4.0.3.tgz",
+ "integrity": "sha512-xelSayHH36ZgE7ZWhli7pW34hNbNl8Ojv5KVmkJD4hBdD3th8Tfk9vYasLM+mXWOZhFkgZfxhLSnrwRr4elSSg==",
+ "requires": {
+ "is-extglob": "^2.1.1"
+ }
+ },
+ "is-negative-zero": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/is-negative-zero/-/is-negative-zero-2.0.2.tgz",
+ "integrity": "sha512-dqJvarLawXsFbNDeJW7zAz8ItJ9cd28YufuuFzh0G8pNHjJMnY08Dv7sYX2uF5UpQOwieAeOExEYAWWfu7ZZUA==",
+ "peer": true
+ },
+ "is-number": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/is-number/-/is-number-7.0.0.tgz",
+ "integrity": "sha512-41Cifkg6e8TylSpdtTpeLVMqvSBEVzTttHvERD741+pnZ8ANv0004MRL43QKPDlK9cGvNp6NZWZUBlbGXYxxng==",
+ "dev": true
+ },
+ "is-number-object": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-number-object/-/is-number-object-1.0.7.tgz",
+ "integrity": "sha512-k1U0IRzLMo7ZlYIfzRu23Oh6MiIFasgpb9X76eqfFZAqwH44UI4KTBvBYIZ1dSL9ZzChTB9ShHfLkR4pdW5krQ==",
+ "peer": true,
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-path-inside": {
+ "version": "3.0.3",
+ "resolved": "https://registry.npmjs.org/is-path-inside/-/is-path-inside-3.0.3.tgz",
+ "integrity": "sha512-Fd4gABb+ycGAmKou8eMftCupSir5lRxqf4aD/vd0cD2qc4HL07OjCeuHMr8Ro4CoMaeCKDB0/ECBOVWjTwUvPQ=="
+ },
+ "is-plain-obj": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/is-plain-obj/-/is-plain-obj-2.1.0.tgz",
+ "integrity": "sha512-YWnfyRwxL/+SsrWYfOpUtz5b3YD+nyfkHvjbcanzk8zgyO4ASD67uVMRt8k5bM4lLMDnXfriRhOpemw+NfT1eA==",
+ "dev": true
+ },
+ "is-regex": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/is-regex/-/is-regex-1.1.4.tgz",
+ "integrity": "sha512-kvRdxDsxZjhzUX07ZnLydzS1TU/TJlTUHHY4YLL87e37oUA49DfkLqgy+VjFocowy29cKvcSiu+kIv728jTTVg==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-shared-array-buffer": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-shared-array-buffer/-/is-shared-array-buffer-1.0.2.tgz",
+ "integrity": "sha512-sqN2UDu1/0y6uvXyStCOzyhAjCSlHceFoMKJW8W9EU9cvic/QdsZ0kEU93HEy3IUEFZIiH/3w+AH/UQbPHNdhA==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "is-string": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/is-string/-/is-string-1.0.7.tgz",
+ "integrity": "sha512-tE2UXzivje6ofPW7l23cjDOMa09gb7xlAqG6jG5ej6uPV32TlWP3NKPigtaGeHNu9fohccRYvIiZMfOOnOYUtg==",
+ "peer": true,
+ "requires": {
+ "has-tostringtag": "^1.0.0"
+ }
+ },
+ "is-symbol": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/is-symbol/-/is-symbol-1.0.4.tgz",
+ "integrity": "sha512-C/CPBqKWnvdcxqIARxyOh4v1UUEOCHpgDa0WYgpKDFMszcrPcffg5uhwSgPCLD2WWxmq6isisz87tzT01tuGhg==",
+ "peer": true,
+ "requires": {
+ "has-symbols": "^1.0.2"
+ }
+ },
+ "is-unicode-supported": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/is-unicode-supported/-/is-unicode-supported-0.1.0.tgz",
+ "integrity": "sha512-knxG2q4UC3u8stRGyAVJCOdxFmv5DZiRcdlIaAQXAbSfJya+OhopNotLQrstBhququ4ZpuKbDc/8S6mgXgPFPw==",
+ "dev": true
+ },
+ "is-weakref": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/is-weakref/-/is-weakref-1.0.2.tgz",
+ "integrity": "sha512-qctsuLZmIQ0+vSSMfoVvyFe2+GSEvnmZ2ezTup1SBse9+twCCeial6EEi3Nc2KFcf6+qz2FBPnjXsk8xhKSaPQ==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2"
+ }
+ },
+ "isexe": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/isexe/-/isexe-2.0.0.tgz",
+ "integrity": "sha512-RHxMLp9lnKHGHRng9QFhRCMbYAcVpn69smSGcq3f36xjgVVWThj4qqLbTLlq7Ssj8B+fIQ1EuCEGI2lKsyQeIw=="
+ },
+ "js-sdsl": {
+ "version": "4.2.0",
+ "resolved": "https://registry.npmjs.org/js-sdsl/-/js-sdsl-4.2.0.tgz",
+ "integrity": "sha512-dyBIzQBDkCqCu+0upx25Y2jGdbTGxE9fshMsCdK0ViOongpV+n5tXRcZY9v7CaVQ79AGS9KA1KHtojxiM7aXSQ=="
+ },
+ "js-tokens": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/js-tokens/-/js-tokens-4.0.0.tgz",
+ "integrity": "sha512-RdJUflcE3cUzKiMqQgsCu06FPu9UdIJO0beYbPhHN4k6apgJtifcoCtT9bcxOpYBtpD2kCM6Sbzg4CausW/PKQ=="
+ },
+ "js-yaml": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/js-yaml/-/js-yaml-4.1.0.tgz",
+ "integrity": "sha512-wpxZs9NoxZaJESJGIZTyDEaYpl0FKSA+FB9aJiyemKhMwkxQg63h4T1KJgUGHpTqPDNRcmmYLugrRjJlBtWvRA==",
+ "requires": {
+ "argparse": "^2.0.1"
+ }
+ },
+ "jsesc": {
+ "version": "2.5.2",
+ "resolved": "https://registry.npmjs.org/jsesc/-/jsesc-2.5.2.tgz",
+ "integrity": "sha512-OYu7XEzjkCQ3C5Ps3QIZsQfNpqoJyZZA99wd9aWd05NCtC5pWOkShK2mkL6HXQR6/Cy2lbNdPlZBpuQHXE63gA=="
+ },
+ "json-schema-traverse": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-0.4.1.tgz",
+ "integrity": "sha512-xbbCH5dCYU5T8LcEhhuh7HJ88HXuW3qsI3Y0zOZFKfZEHcpWiHU/Jxzk629Brsab/mMiHQti9wMP+845RPe3Vg=="
+ },
+ "json-stable-stringify-without-jsonify": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/json-stable-stringify-without-jsonify/-/json-stable-stringify-without-jsonify-1.0.1.tgz",
+ "integrity": "sha512-Bdboy+l7tA3OGW6FjyFHWkP5LuByj1Tk33Ljyq0axyzdk9//JSi2u3fP1QSmd1KNwq6VOKYGlAu87CisVir6Pw=="
+ },
+ "json5": {
+ "version": "2.2.1",
+ "resolved": "https://registry.npmjs.org/json5/-/json5-2.2.1.tgz",
+ "integrity": "sha512-1hqLFMSrGHRHxav9q9gNjJ5EXznIxGVO09xQRrwplcS8qs28pZ8s8hupZAmqDwZUmVZ2Qb2jnyPOWcDH8m8dlA=="
+ },
+ "jsx-ast-utils": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/jsx-ast-utils/-/jsx-ast-utils-3.3.3.tgz",
+ "integrity": "sha512-fYQHZTZ8jSfmWZ0iyzfwiU4WDX4HpHbMCZ3gPlWYiCl3BoeOTsqKBqnTVfH2rYT7eP5c3sVbeSPHnnJOaTrWiw==",
+ "peer": true,
+ "requires": {
+ "array-includes": "^3.1.5",
+ "object.assign": "^4.1.3"
+ }
+ },
+ "levn": {
+ "version": "0.4.1",
+ "resolved": "https://registry.npmjs.org/levn/-/levn-0.4.1.tgz",
+ "integrity": "sha512-+bT2uH4E5LGE7h/n3evcS/sQlJXCpIp6ym8OWJ5eV6+67Dsql/LaaT7qJBAt2rzfoa/5QBGBhxDix1dMt2kQKQ==",
+ "requires": {
+ "prelude-ls": "^1.2.1",
+ "type-check": "~0.4.0"
+ }
+ },
+ "locate-path": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/locate-path/-/locate-path-6.0.0.tgz",
+ "integrity": "sha512-iPZK6eYjbxRu3uB4/WZ3EsEIMJFMqAoopl3R+zuq0UjcAm/MO6KCweDgPfP3elTztoKP3KtnVHxTn2NHBSDVUw==",
+ "requires": {
+ "p-locate": "^5.0.0"
+ }
+ },
+ "lodash": {
+ "version": "4.17.21",
+ "resolved": "https://registry.npmjs.org/lodash/-/lodash-4.17.21.tgz",
+ "integrity": "sha512-v2kDEe57lecTulaDIuNTPy3Ry4gLGJ6Z1O3vE1krgXZNrsQ+LFTGHVxVjcXPs17LhbZVGedAJv8XZ1tvj5FvSg=="
+ },
+ "lodash.merge": {
+ "version": "4.6.2",
+ "resolved": "https://registry.npmjs.org/lodash.merge/-/lodash.merge-4.6.2.tgz",
+ "integrity": "sha512-0KpjqXRVvrYyCsX1swR/XTK0va6VQkQM6MNo7PqW77ByjAhoARA8EfrP1N4+KlKj8YS0ZUCtRT/YUuhyYDujIQ=="
+ },
+ "lodash.truncate": {
+ "version": "4.4.2",
+ "resolved": "https://registry.npmjs.org/lodash.truncate/-/lodash.truncate-4.4.2.tgz",
+ "integrity": "sha512-jttmRe7bRse52OsWIMDLaXxWqRAmtIUccAQ3garviCqJjafXOfNMO0yMfNpdD6zbGaTU0P5Nz7e7gAT6cKmJRw==",
+ "peer": true
+ },
+ "log-symbols": {
+ "version": "4.1.0",
+ "resolved": "https://registry.npmjs.org/log-symbols/-/log-symbols-4.1.0.tgz",
+ "integrity": "sha512-8XPvpAA8uyhfteu8pIvQxpJZ7SYYdpUivZpGy6sFsBuKRY/7rQGavedeB8aK+Zkyq6upMFVL/9AW6vOYzfRyLg==",
+ "dev": true,
+ "requires": {
+ "chalk": "^4.1.0",
+ "is-unicode-supported": "^0.1.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "chalk": {
+ "version": "4.1.2",
+ "resolved": "https://registry.npmjs.org/chalk/-/chalk-4.1.2.tgz",
+ "integrity": "sha512-oKnbhFyRIXpUuez8iBMmyEa4nbj4IOQyuhc/wy9kY7/WVPcwIO9VA668Pu8RkO7+0G76SLROeyw9CpQ061i4mA==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.1.0",
+ "supports-color": "^7.1.0"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "7.2.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-7.2.0.tgz",
+ "integrity": "sha512-qpCAvRl9stuOHveKsn7HncJRvv501qIacKzQlO/+Lwxc9+0q2wLyv4Dfvt80/DPn2pqOBsJdDiogXGR9+OvwRw==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "loose-envify": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/loose-envify/-/loose-envify-1.4.0.tgz",
+ "integrity": "sha512-lyuxPGr/Wfhrlem2CL/UcnUc1zcqKAImBDzukY7Y5F/yQiNdko6+fRLevlw1HgMySw7f611UIY408EtxRSoK3Q==",
+ "peer": true,
+ "requires": {
+ "js-tokens": "^3.0.0 || ^4.0.0"
+ }
+ },
+ "lru-cache": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/lru-cache/-/lru-cache-6.0.0.tgz",
+ "integrity": "sha512-Jo6dJ04CmSjuznwJSS3pUeWmd/H0ffTlkXXgwZi+eq1UCmqQwCh+eLsYOYCwY991i2Fah4h1BEMCx4qThGbsiA==",
+ "peer": true,
+ "requires": {
+ "yallist": "^4.0.0"
+ }
+ },
+ "minimatch": {
+ "version": "3.1.2",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-3.1.2.tgz",
+ "integrity": "sha512-J7p63hRiAjw1NDEww1W7i37+ByIrOWO5XQQAzZ3VOcL0PNybwpfmV/N05zFAzwQ9USyEcX6t3UO+K5aqBQOIHw==",
+ "requires": {
+ "brace-expansion": "^1.1.7"
+ }
+ },
+ "mocha": {
+ "version": "10.1.0",
+ "resolved": "https://registry.npmjs.org/mocha/-/mocha-10.1.0.tgz",
+ "integrity": "sha512-vUF7IYxEoN7XhQpFLxQAEMtE4W91acW4B6En9l97MwE9stL1A9gusXfoHZCLVHDUJ/7V5+lbCM6yMqzo5vNymg==",
+ "dev": true,
+ "requires": {
+ "ansi-colors": "4.1.1",
+ "browser-stdout": "1.3.1",
+ "chokidar": "3.5.3",
+ "debug": "4.3.4",
+ "diff": "5.0.0",
+ "escape-string-regexp": "4.0.0",
+ "find-up": "5.0.0",
+ "glob": "7.2.0",
+ "he": "1.2.0",
+ "js-yaml": "4.1.0",
+ "log-symbols": "4.1.0",
+ "minimatch": "5.0.1",
+ "ms": "2.1.3",
+ "nanoid": "3.3.3",
+ "serialize-javascript": "6.0.0",
+ "strip-json-comments": "3.1.1",
+ "supports-color": "8.1.1",
+ "workerpool": "6.2.1",
+ "yargs": "16.2.0",
+ "yargs-parser": "20.2.4",
+ "yargs-unparser": "2.0.0"
+ },
+ "dependencies": {
+ "brace-expansion": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/brace-expansion/-/brace-expansion-2.0.1.tgz",
+ "integrity": "sha512-XnAIvQ8eM+kC6aULx6wuQiwVsnzsi9d3WxzV3FpWTGA19F621kwdbsAcFKXgKUHZWsy+mY6iL1sHTxWEFCytDA==",
+ "dev": true,
+ "requires": {
+ "balanced-match": "^1.0.0"
+ }
+ },
+ "escape-string-regexp": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/escape-string-regexp/-/escape-string-regexp-4.0.0.tgz",
+ "integrity": "sha512-TtpcNJ3XAzx3Gq8sWRzJaVajRs0uVxA2YAkdb1jm2YkPz4G6egUFAyA3n5vtEIZefPk5Wa4UXbKuS5fKkJWdgA==",
+ "dev": true
+ },
+ "has-flag": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/has-flag/-/has-flag-4.0.0.tgz",
+ "integrity": "sha512-EykJT/Q1KjTWctppgIAgfSO0tKVuZUjhgMr17kqTumMl6Afv3EISleU7qZUzoXDFTAHTDC4NOoG/ZxU3EvlMPQ==",
+ "dev": true
+ },
+ "minimatch": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/minimatch/-/minimatch-5.0.1.tgz",
+ "integrity": "sha512-nLDxIFRyhDblz3qMuq+SoRZED4+miJ/G+tdDrjkkkRnjAsBexeGpgjLEQ0blJy7rHhR2b93rhQY4SvyWu9v03g==",
+ "dev": true,
+ "requires": {
+ "brace-expansion": "^2.0.1"
+ }
+ },
+ "ms": {
+ "version": "2.1.3",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.3.tgz",
+ "integrity": "sha512-6FlzubTLZG3J2a/NVCAleEhjzq5oxgHyaCU9yYXvcLsvoVaHJq/s5xXI6/XXP6tz7R9xAOtHnSO/tXtF3WRTlA==",
+ "dev": true
+ },
+ "supports-color": {
+ "version": "8.1.1",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-8.1.1.tgz",
+ "integrity": "sha512-MpUEN2OodtUzxvKQl72cUF7RQ5EiHsGvSsVG0ia9c5RbWGL2CI4C7EpPS8UTBIplnlzZiNuV56w+FuNxy3ty2Q==",
+ "dev": true,
+ "requires": {
+ "has-flag": "^4.0.0"
+ }
+ }
+ }
+ },
+ "ms": {
+ "version": "2.1.2",
+ "resolved": "https://registry.npmjs.org/ms/-/ms-2.1.2.tgz",
+ "integrity": "sha512-sGkPx+VjMtmA6MX27oA4FBFELFCZZ4S4XqeGOXCv68tT+jb3vk/RyaKWP0PTKyWtmLSM0b+adUTEvbs1PEaH2w=="
+ },
+ "multi-ini": {
+ "version": "2.3.2",
+ "resolved": "https://registry.npmjs.org/multi-ini/-/multi-ini-2.3.2.tgz",
+ "integrity": "sha512-zuznIotGjtc8AXfWwX5/pfQI6JadxR/kN7zA1W8qqomk/7zKHMW54ik052dqV3bPzWLucysvPgJXEySsctUUWQ==",
+ "requires": {
+ "@babel/runtime": "^7.0.0",
+ "lodash": "^4.0.0"
+ }
+ },
+ "nanoid": {
+ "version": "3.3.3",
+ "resolved": "https://registry.npmjs.org/nanoid/-/nanoid-3.3.3.tgz",
+ "integrity": "sha512-p1sjXuopFs0xg+fPASzQ28agW1oHD7xDsd9Xkf3T15H3c/cifrFHVwrh74PdoklAPi+i7MdRsE47vm2r6JoB+w==",
+ "dev": true
+ },
+ "natural-compare": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/natural-compare/-/natural-compare-1.4.0.tgz",
+ "integrity": "sha512-OWND8ei3VtNC9h7V60qff3SVobHr996CTwgxubgyQYEpg290h9J0buyECNNJexkFm5sOajh5G116RYA1c8ZMSw=="
+ },
+ "node-releases": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/node-releases/-/node-releases-2.0.6.tgz",
+ "integrity": "sha512-PiVXnNuFm5+iYkLBNeq5211hvO38y63T0i2KKh2KnUs3RpzJ+JtODFjkD8yjLwnDkTYF1eKXheUwdssR+NRZdg=="
+ },
+ "normalize-path": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/normalize-path/-/normalize-path-3.0.0.tgz",
+ "integrity": "sha512-6eZs5Ls3WtCisHWp9S2GUy8dqkpGi4BVSz3GaqiE6ezub0512ESztXUwUB6C6IKbQkY2Pnb/mD4WYojCRwcwLA==",
+ "dev": true
+ },
+ "object-assign": {
+ "version": "4.1.1",
+ "resolved": "https://registry.npmjs.org/object-assign/-/object-assign-4.1.1.tgz",
+ "integrity": "sha512-rJgTQnkUnH1sFw8yT6VSU3zD3sWmu6sZhIseY8VX+GRu3P6F7Fu+JNDoXfklElbLJSnc3FUQHVe4cU5hj+BcUg==",
+ "peer": true
+ },
+ "object-inspect": {
+ "version": "1.12.2",
+ "resolved": "https://registry.npmjs.org/object-inspect/-/object-inspect-1.12.2.tgz",
+ "integrity": "sha512-z+cPxW0QGUp0mcqcsgQyLVRDoXFQbXOwBaqyF7VIgI4TWNQsDHrBpUQslRmIfAoYWdYzs6UlKJtB2XJpTaNSpQ==",
+ "peer": true
+ },
+ "object-keys": {
+ "version": "1.1.1",
+ "resolved": "https://registry.npmjs.org/object-keys/-/object-keys-1.1.1.tgz",
+ "integrity": "sha512-NuAESUOUMrlIXOfHKzD6bpPu3tYt3xvjNdRIQ+FeT0lNb4K8WR70CaDxhuNguS2XG+GjkyMwOzsN5ZktImfhLA==",
+ "peer": true
+ },
+ "object.assign": {
+ "version": "4.1.4",
+ "resolved": "https://registry.npmjs.org/object.assign/-/object.assign-4.1.4.tgz",
+ "integrity": "sha512-1mxKf0e58bvyjSCtKYY4sRe9itRk3PJpquJOjeIkz885CczcI4IvJJDLPS72oowuSh+pBxUFROpX+TU++hxhZQ==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "has-symbols": "^1.0.3",
+ "object-keys": "^1.1.1"
+ }
+ },
+ "object.entries": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.entries/-/object.entries-1.1.6.tgz",
+ "integrity": "sha512-leTPzo4Zvg3pmbQ3rDK69Rl8GQvIqMWubrkxONG9/ojtFE2rD9fjMKfSI5BxW3osRH1m6VdzmqK8oAY9aT4x5w==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "object.fromentries": {
+ "version": "2.0.6",
+ "resolved": "https://registry.npmjs.org/object.fromentries/-/object.fromentries-2.0.6.tgz",
+ "integrity": "sha512-VciD13dswC4j1Xt5394WR4MzmAQmlgN72phd/riNp9vtD7tp4QQWJ0R4wvclXcafgcYK8veHRed2W6XeGBvcfg==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "object.values": {
+ "version": "1.1.6",
+ "resolved": "https://registry.npmjs.org/object.values/-/object.values-1.1.6.tgz",
+ "integrity": "sha512-FVVTkD1vENCsAcwNs9k6jea2uHC/X0+JcjG8YA60FN5CMaJmG95wT9jek/xX9nornqGRrBkKtzuAu2wuHpKqvw==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "once": {
+ "version": "1.4.0",
+ "resolved": "https://registry.npmjs.org/once/-/once-1.4.0.tgz",
+ "integrity": "sha512-lNaJgI+2Q5URQBkccEKHTQOPaXdUxnZZElQTZY0MFUAuaEqe1E+Nyvgdz/aIyNi6Z9MzO5dv1H8n58/GELp3+w==",
+ "requires": {
+ "wrappy": "1"
+ }
+ },
+ "optionator": {
+ "version": "0.9.1",
+ "resolved": "https://registry.npmjs.org/optionator/-/optionator-0.9.1.tgz",
+ "integrity": "sha512-74RlY5FCnhq4jRxVUPKDaRwrVNXMqsGsiW6AJw4XK8hmtm10wC0ypZBLw5IIp85NZMr91+qd1RvvENwg7jjRFw==",
+ "requires": {
+ "deep-is": "^0.1.3",
+ "fast-levenshtein": "^2.0.6",
+ "levn": "^0.4.1",
+ "prelude-ls": "^1.2.1",
+ "type-check": "^0.4.0",
+ "word-wrap": "^1.2.3"
+ }
+ },
+ "p-limit": {
+ "version": "3.1.0",
+ "resolved": "https://registry.npmjs.org/p-limit/-/p-limit-3.1.0.tgz",
+ "integrity": "sha512-TYOanM3wGwNGsZN2cVTYPArw454xnXj5qmWF1bEoAc4+cU/ol7GVh7odevjp1FNHduHc3KZMcFduxU5Xc6uJRQ==",
+ "requires": {
+ "yocto-queue": "^0.1.0"
+ }
+ },
+ "p-locate": {
+ "version": "5.0.0",
+ "resolved": "https://registry.npmjs.org/p-locate/-/p-locate-5.0.0.tgz",
+ "integrity": "sha512-LaNjtRWUBY++zB5nE/NwcaoMylSPk+S+ZHNB1TzdbMJMny6dynpAGt7X/tl/QYq3TIeE6nxHppbo2LGymrG5Pw==",
+ "requires": {
+ "p-limit": "^3.0.2"
+ }
+ },
+ "parent-module": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/parent-module/-/parent-module-1.0.1.tgz",
+ "integrity": "sha512-GQ2EWRpQV8/o+Aw8YqtfZZPfNRWZYkbidE9k5rpl/hC3vtHHBfGm2Ifi6qWV+coDGkrUKZAxE3Lot5kcsRlh+g==",
+ "requires": {
+ "callsites": "^3.0.0"
+ }
+ },
+ "path-exists": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/path-exists/-/path-exists-4.0.0.tgz",
+ "integrity": "sha512-ak9Qy5Q7jYb2Wwcey5Fpvg2KoAc/ZIhLSLOSBmRmygPsGwkVVt0fZa0qrtMz+m6tJTAHfZQ8FnmB4MG4LWy7/w=="
+ },
+ "path-is-absolute": {
+ "version": "1.0.1",
+ "resolved": "https://registry.npmjs.org/path-is-absolute/-/path-is-absolute-1.0.1.tgz",
+ "integrity": "sha512-AVbw3UJ2e9bq64vSaS9Am0fje1Pa8pbGqTTsmXfaIiMpnr5DlDhfJOuLj9Sf95ZPVDAUerDfEk88MPmPe7UCQg=="
+ },
+ "path-key": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/path-key/-/path-key-3.1.1.tgz",
+ "integrity": "sha512-ojmeN0qd+y0jszEtoY48r0Peq5dwMEkIlCOu6Q5f41lfkswXuKtYrhgoTpLnyIcHm24Uhqx+5Tqm2InSwLhE6Q=="
+ },
+ "path-parse": {
+ "version": "1.0.7",
+ "resolved": "https://registry.npmjs.org/path-parse/-/path-parse-1.0.7.tgz",
+ "integrity": "sha512-LDJzPVEEEPR+y48z93A0Ed0yXb8pAByGWo/k5YYdYgpY2/2EsOsksJrq7lOHxryrVOn1ejG6oAp8ahvOIQD8sw==",
+ "peer": true
+ },
+ "picocolors": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/picocolors/-/picocolors-1.0.0.tgz",
+ "integrity": "sha512-1fygroTLlHu66zi26VoTDv8yRgm0Fccecssto+MhsZ0D/DGW2sm8E8AjW7NU5VVTRt5GxbeZ5qBuJr+HyLYkjQ=="
+ },
+ "picomatch": {
+ "version": "2.3.1",
+ "resolved": "https://registry.npmjs.org/picomatch/-/picomatch-2.3.1.tgz",
+ "integrity": "sha512-JU3teHTNjmE2VCGFzuY8EXzCDVwEqB2a8fsIvwaStHhAWJEeVd1o1QD80CU6+ZdEXXSLbSsuLwJjkCBWqRQUVA==",
+ "dev": true
+ },
+ "prelude-ls": {
+ "version": "1.2.1",
+ "resolved": "https://registry.npmjs.org/prelude-ls/-/prelude-ls-1.2.1.tgz",
+ "integrity": "sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g=="
+ },
+ "prettier": {
+ "version": "1.19.1",
+ "resolved": "https://registry.npmjs.org/prettier/-/prettier-1.19.1.tgz",
+ "integrity": "sha512-s7PoyDv/II1ObgQunCbB9PdLmUcBZcnWOcxDh7O0N/UwDEsHyqkW+Qh28jW+mVuCdx7gLB0BotYI1Y6uI9iyew==",
+ "peer": true
+ },
+ "prettier-linter-helpers": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/prettier-linter-helpers/-/prettier-linter-helpers-1.0.0.tgz",
+ "integrity": "sha512-GbK2cP9nraSSUF9N2XwUwqfzlAFlMNYYl+ShE/V+H8a9uNl/oUqB1w2EL54Jh0OlyRSd8RfWYJ3coVS4TROP2w==",
+ "peer": true,
+ "requires": {
+ "fast-diff": "^1.1.2"
+ }
+ },
+ "progress": {
+ "version": "2.0.3",
+ "resolved": "https://registry.npmjs.org/progress/-/progress-2.0.3.tgz",
+ "integrity": "sha512-7PiHtLll5LdnKIMw100I+8xJXR5gW2QwWYkT6iJva0bXitZKa/XMrSbdmg3r2Xnaidz9Qumd0VPaMrZlF9V9sA==",
+ "peer": true
+ },
+ "prop-types": {
+ "version": "15.8.1",
+ "resolved": "https://registry.npmjs.org/prop-types/-/prop-types-15.8.1.tgz",
+ "integrity": "sha512-oj87CgZICdulUohogVAR7AjlC0327U4el4L6eAvOqCeudMDVU0NThNaV+b9Df4dXgSP1gXMTnPdhfe/2qDH5cg==",
+ "peer": true,
+ "requires": {
+ "loose-envify": "^1.4.0",
+ "object-assign": "^4.1.1",
+ "react-is": "^16.13.1"
+ }
+ },
+ "punycode": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/punycode/-/punycode-2.1.1.tgz",
+ "integrity": "sha512-XRsRjdf+j5ml+y/6GKHPZbrF/8p2Yga0JPtdqTIY2Xe5ohJPD9saDJJLPvp9+NSBprVvevdXZybnj2cv8OEd0A=="
+ },
+ "queue-microtask": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/queue-microtask/-/queue-microtask-1.2.3.tgz",
+ "integrity": "sha512-NuaNSa6flKT5JaSYQzJok04JzTL1CA6aGhv5rfLW3PgqA+M2ChpZQnAC8h8i4ZFkBS8X5RqkDBHA7r4hej3K9A=="
+ },
+ "randombytes": {
+ "version": "2.1.0",
+ "resolved": "https://registry.npmjs.org/randombytes/-/randombytes-2.1.0.tgz",
+ "integrity": "sha512-vYl3iOX+4CKUWuxGi9Ukhie6fsqXqS9FE2Zaic4tNFD2N2QQaXOMFbuKK4QmDHC0JO6B1Zp41J0LpT0oR68amQ==",
+ "dev": true,
+ "requires": {
+ "safe-buffer": "^5.1.0"
+ }
+ },
+ "react-is": {
+ "version": "16.13.1",
+ "resolved": "https://registry.npmjs.org/react-is/-/react-is-16.13.1.tgz",
+ "integrity": "sha512-24e6ynE2H+OKt4kqsOvNd8kBpV65zoxbA4BVsEOB3ARVWQki/DHzaUoC5KuON/BiccDaCCTZBuOcfZs70kR8bQ==",
+ "peer": true
+ },
+ "readdirp": {
+ "version": "3.6.0",
+ "resolved": "https://registry.npmjs.org/readdirp/-/readdirp-3.6.0.tgz",
+ "integrity": "sha512-hOS089on8RduqdbhvQ5Z37A0ESjsqz6qnRcffsMU3495FuTdqSm+7bhJ29JvIOsBDEEnan5DPu9t3To9VRlMzA==",
+ "dev": true,
+ "requires": {
+ "picomatch": "^2.2.1"
+ }
+ },
+ "regenerator-runtime": {
+ "version": "0.13.11",
+ "resolved": "https://registry.npmjs.org/regenerator-runtime/-/regenerator-runtime-0.13.11.tgz",
+ "integrity": "sha512-kY1AZVr2Ra+t+piVaJ4gxaFaReZVH40AKNo7UCX6W+dEwBo/2oZJzqfuN1qLq1oL45o56cPaTXELwrTh8Fpggg=="
+ },
+ "regexp.prototype.flags": {
+ "version": "1.4.3",
+ "resolved": "https://registry.npmjs.org/regexp.prototype.flags/-/regexp.prototype.flags-1.4.3.tgz",
+ "integrity": "sha512-fjggEOO3slI6Wvgjwflkc4NFRCTZAu5CnNfBd5qOMYhWdn67nJBBu34/TkD++eeFmd8C9r9jfXJ27+nSiRkSUA==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.3",
+ "functions-have-names": "^1.2.2"
+ }
+ },
+ "regexpp": {
+ "version": "3.2.0",
+ "resolved": "https://registry.npmjs.org/regexpp/-/regexpp-3.2.0.tgz",
+ "integrity": "sha512-pq2bWo9mVD43nbts2wGv17XLiNLya+GklZ8kaDLV2Z08gDCsGpnKn9BFMepvWuHCbyVvY7J5o5+BVvoQbmlJLg=="
+ },
+ "require-directory": {
+ "version": "2.1.1",
+ "resolved": "https://registry.npmjs.org/require-directory/-/require-directory-2.1.1.tgz",
+ "integrity": "sha512-fGxEI7+wsG9xrvdjsrlmL22OMTTiHRwAMroiEeMgq8gzoLC/PQr7RsRDSTLUg/bZAZtF+TVIkHc6/4RIKrui+Q==",
+ "dev": true
+ },
+ "require-from-string": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/require-from-string/-/require-from-string-2.0.2.tgz",
+ "integrity": "sha512-Xf0nWe6RseziFMu+Ap9biiUbmplq6S9/p+7w7YXP/JBHhrUDDUhwa+vANyubuqfZWTveU//DYVGsDG7RKL/vEw==",
+ "peer": true
+ },
+ "resolve": {
+ "version": "1.22.1",
+ "resolved": "https://registry.npmjs.org/resolve/-/resolve-1.22.1.tgz",
+ "integrity": "sha512-nBpuuYuY5jFsli/JIs1oldw6fOQCBioohqWZg/2hiaOybXOft4lonv85uDOKXdf8rhyK159cxU5cDcK/NKk8zw==",
+ "peer": true,
+ "requires": {
+ "is-core-module": "^2.9.0",
+ "path-parse": "^1.0.7",
+ "supports-preserve-symlinks-flag": "^1.0.0"
+ }
+ },
+ "resolve-from": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/resolve-from/-/resolve-from-4.0.0.tgz",
+ "integrity": "sha512-pb/MYmXstAkysRFx8piNI1tGFNQIFA3vkE3Gq4EuA1dF6gHp/+vgZqsCGJapvy8N3Q+4o7FwvquPJcnZ7RYy4g=="
+ },
+ "ret": {
+ "version": "0.1.15",
+ "resolved": "https://registry.npmjs.org/ret/-/ret-0.1.15.tgz",
+ "integrity": "sha512-TTlYpa+OL+vMMNG24xSlQGEJ3B/RzEfUlLct7b5G/ytav+wPrplCpVMFuwzXbkecJrb6IYo1iFb0S9v37754mg==",
+ "peer": true
+ },
+ "reusify": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/reusify/-/reusify-1.0.4.tgz",
+ "integrity": "sha512-U9nH88a3fc/ekCF1l0/UP1IosiuIjyTh7hBvXVMHYgVcfGvt897Xguj2UOLDeI5BG2m7/uwyaLVT6fbtCwTyzw=="
+ },
+ "rimraf": {
+ "version": "3.0.2",
+ "resolved": "https://registry.npmjs.org/rimraf/-/rimraf-3.0.2.tgz",
+ "integrity": "sha512-JZkJMZkAGFFPP2YqXZXPbMlMBgsxzE8ILs4lMIX/2o0L9UBw9O/Y3o6wFw/i9YLapcUJWwqbi3kdxIPdC62TIA==",
+ "requires": {
+ "glob": "^7.1.3"
+ }
+ },
+ "run-parallel": {
+ "version": "1.2.0",
+ "resolved": "https://registry.npmjs.org/run-parallel/-/run-parallel-1.2.0.tgz",
+ "integrity": "sha512-5l4VyZR86LZ/lDxZTR6jqL8AFE2S0IFLMP26AbjsLVADxHdhB/c0GUsH+y39UfCi3dzz8OlQuPmnaJOMoDHQBA==",
+ "requires": {
+ "queue-microtask": "^1.2.2"
+ }
+ },
+ "safe-buffer": {
+ "version": "5.2.1",
+ "resolved": "https://registry.npmjs.org/safe-buffer/-/safe-buffer-5.2.1.tgz",
+ "integrity": "sha512-rp3So07KcdmmKbGvgaNxQSJr7bGVSVk5S9Eq1F+ppbRo70+YeaDxkw5Dd8NPN+GD6bjnYm2VuPuCXmpuYvmCXQ==",
+ "dev": true
+ },
+ "safe-regex": {
+ "version": "1.1.0",
+ "resolved": "https://registry.npmjs.org/safe-regex/-/safe-regex-1.1.0.tgz",
+ "integrity": "sha512-aJXcif4xnaNUzvUuC5gcb46oTS7zvg4jpMTnuqtrEPlR3vFr4pxtdTwaF1Qs3Enjn9HK+ZlwQui+a7z0SywIzg==",
+ "peer": true,
+ "requires": {
+ "ret": "~0.1.10"
+ }
+ },
+ "safe-regex-test": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/safe-regex-test/-/safe-regex-test-1.0.0.tgz",
+ "integrity": "sha512-JBUUzyOgEwXQY1NuPtvcj/qcBDbDmEvWufhlnXZIm75DEHp+afM1r1ujJpJsV/gSM4t59tpDyPi1sd6ZaPFfsA==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "get-intrinsic": "^1.1.3",
+ "is-regex": "^1.1.4"
+ }
+ },
+ "semver": {
+ "version": "6.3.0",
+ "resolved": "https://registry.npmjs.org/semver/-/semver-6.3.0.tgz",
+ "integrity": "sha512-b39TBaTSfV6yBrapU89p5fKekE2m/NwnDocOVruQFS1/veMgdzuPcnOM34M6CwxW8jH/lxEa5rBoDeUwu5HHTw=="
+ },
+ "serialize-javascript": {
+ "version": "6.0.0",
+ "resolved": "https://registry.npmjs.org/serialize-javascript/-/serialize-javascript-6.0.0.tgz",
+ "integrity": "sha512-Qr3TosvguFt8ePWqsvRfrKyQXIiW+nGbYpy8XK24NQHE83caxWt+mIymTT19DGFbNWNLfEwsrkSmN64lVWB9ag==",
+ "dev": true,
+ "requires": {
+ "randombytes": "^2.1.0"
+ }
+ },
+ "shebang-command": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-command/-/shebang-command-2.0.0.tgz",
+ "integrity": "sha512-kHxr2zZpYtdmrN1qDjrrX/Z1rR1kG8Dx+gkpK1G4eXmvXswmcE1hTWBWYUzlraYw1/yZp6YuDY77YtvbN0dmDA==",
+ "requires": {
+ "shebang-regex": "^3.0.0"
+ }
+ },
+ "shebang-regex": {
+ "version": "3.0.0",
+ "resolved": "https://registry.npmjs.org/shebang-regex/-/shebang-regex-3.0.0.tgz",
+ "integrity": "sha512-7++dFhtcx3353uBaq8DDR4NuxBetBzC7ZQOhmTQInHEd6bSrXdiEyzCvG07Z44UYdLShWUyXt5M/yhz8ekcb1A=="
+ },
+ "side-channel": {
+ "version": "1.0.4",
+ "resolved": "https://registry.npmjs.org/side-channel/-/side-channel-1.0.4.tgz",
+ "integrity": "sha512-q5XPytqFEIKHkGdiMIrY10mvLRvnQh42/+GoBlFW3b2LXLE2xxJpZFdm94we0BaoV3RwJyGqg5wS7epxTv0Zvw==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.0",
+ "get-intrinsic": "^1.0.2",
+ "object-inspect": "^1.9.0"
+ }
+ },
+ "slice-ansi": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/slice-ansi/-/slice-ansi-4.0.0.tgz",
+ "integrity": "sha512-qMCMfhY040cVHT43K9BFygqYbUPFZKHOg7K73mtTWJRb8pyP3fzf4Ixd5SzdEJQ6MRUg/WBnOLxghZtKKurENQ==",
+ "peer": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "astral-regex": "^2.0.0",
+ "is-fullwidth-code-point": "^3.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "peer": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "peer": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "peer": true
+ }
+ }
+ },
+ "sprintf-js": {
+ "version": "1.0.3",
+ "resolved": "https://registry.npmjs.org/sprintf-js/-/sprintf-js-1.0.3.tgz",
+ "integrity": "sha512-D9cPgkvLlV3t3IzL0D0YLvGA9Ahk4PcvVwUbN0dSGr1aP0Nrt4AEnTUbuGvquEC0mA64Gqt1fzirlRs5ibXx8g==",
+ "peer": true
+ },
+ "string-width": {
+ "version": "4.2.3",
+ "resolved": "https://registry.npmjs.org/string-width/-/string-width-4.2.3.tgz",
+ "integrity": "sha512-wKyQRQpjJ0sIp62ErSZdGsjMJWsap5oRNihHhu6G7JVO/9jIB6UyevL+tXuOqrng8j/cxKTWyWUwvSTriiZz/g==",
+ "requires": {
+ "emoji-regex": "^8.0.0",
+ "is-fullwidth-code-point": "^3.0.0",
+ "strip-ansi": "^6.0.1"
+ }
+ },
+ "string.prototype.matchall": {
+ "version": "4.0.8",
+ "resolved": "https://registry.npmjs.org/string.prototype.matchall/-/string.prototype.matchall-4.0.8.tgz",
+ "integrity": "sha512-6zOCOcJ+RJAQshcTvXPHoxoQGONa3e/Lqx90wUA+wEzX78sg5Bo+1tQo4N0pohS0erG9qtCqJDjNCQBjeWVxyg==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4",
+ "get-intrinsic": "^1.1.3",
+ "has-symbols": "^1.0.3",
+ "internal-slot": "^1.0.3",
+ "regexp.prototype.flags": "^1.4.3",
+ "side-channel": "^1.0.4"
+ }
+ },
+ "string.prototype.trimend": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimend/-/string.prototype.trimend-1.0.6.tgz",
+ "integrity": "sha512-JySq+4mrPf9EsDBEDYMOb/lM7XQLulwg5R/m1r0PXEFqrV0qHvl58sdTilSXtKOflCsK2E8jxf+GKC0T07RWwQ==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "string.prototype.trimstart": {
+ "version": "1.0.6",
+ "resolved": "https://registry.npmjs.org/string.prototype.trimstart/-/string.prototype.trimstart-1.0.6.tgz",
+ "integrity": "sha512-omqjMDaY92pbn5HOX7f9IccLA+U1tA9GvtU4JrodiXFfYB7jPzzHpRzpglLAjtUV6bB557zwClJezTqnAiYnQA==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "define-properties": "^1.1.4",
+ "es-abstract": "^1.20.4"
+ }
+ },
+ "strip-ansi": {
+ "version": "6.0.1",
+ "resolved": "https://registry.npmjs.org/strip-ansi/-/strip-ansi-6.0.1.tgz",
+ "integrity": "sha512-Y38VPSHcqkFrCpFnQ9vuSXmquuv5oXOKpGeT6aGrr3o3Gc9AlVa6JBfUSOCnbxGGZF+/0ooI7KrPuUSztUdU5A==",
+ "requires": {
+ "ansi-regex": "^5.0.1"
+ }
+ },
+ "strip-json-comments": {
+ "version": "3.1.1",
+ "resolved": "https://registry.npmjs.org/strip-json-comments/-/strip-json-comments-3.1.1.tgz",
+ "integrity": "sha512-6fPc+R4ihwqP6N/aIv2f1gMH8lOVtWQHoqC4yK6oSDVVocumAsfCqjkXnqiYMhmMwS/mEHLp7Vehlt3ql6lEig=="
+ },
+ "supports-color": {
+ "version": "5.5.0",
+ "resolved": "https://registry.npmjs.org/supports-color/-/supports-color-5.5.0.tgz",
+ "integrity": "sha512-QjVjwdXIt408MIiAqCX4oUKsgU2EqAGzs2Ppkm4aQYbjm+ZEWEcW4SfFNTr4uMNZma0ey4f5lgLrkB0aX0QMow==",
+ "requires": {
+ "has-flag": "^3.0.0"
+ }
+ },
+ "supports-preserve-symlinks-flag": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/supports-preserve-symlinks-flag/-/supports-preserve-symlinks-flag-1.0.0.tgz",
+ "integrity": "sha512-ot0WnXS9fgdkgIcePe6RHNk1WA8+muPa6cSjeR3V8K27q9BB1rTE3R1p7Hv0z1ZyAc8s6Vvv8DIyWf681MAt0w==",
+ "peer": true
+ },
+ "table": {
+ "version": "6.8.1",
+ "resolved": "https://registry.npmjs.org/table/-/table-6.8.1.tgz",
+ "integrity": "sha512-Y4X9zqrCftUhMeH2EptSSERdVKt/nEdijTOacGD/97EKjhQ/Qs8RTlEGABSJNNN8lac9kheH+af7yAkEWlgneA==",
+ "peer": true,
+ "requires": {
+ "ajv": "^8.0.1",
+ "lodash.truncate": "^4.4.2",
+ "slice-ansi": "^4.0.0",
+ "string-width": "^4.2.3",
+ "strip-ansi": "^6.0.1"
+ },
+ "dependencies": {
+ "ajv": {
+ "version": "8.11.2",
+ "resolved": "https://registry.npmjs.org/ajv/-/ajv-8.11.2.tgz",
+ "integrity": "sha512-E4bfmKAhGiSTvMfL1Myyycaub+cUEU2/IvpylXkUu7CHBkBj1f/ikdzbD7YQ6FKUbixDxeYvB/xY4fvyroDlQg==",
+ "peer": true,
+ "requires": {
+ "fast-deep-equal": "^3.1.1",
+ "json-schema-traverse": "^1.0.0",
+ "require-from-string": "^2.0.2",
+ "uri-js": "^4.2.2"
+ }
+ },
+ "json-schema-traverse": {
+ "version": "1.0.0",
+ "resolved": "https://registry.npmjs.org/json-schema-traverse/-/json-schema-traverse-1.0.0.tgz",
+ "integrity": "sha512-NM8/P9n3XjXhIZn1lLhkFaACTOURQXjWhV4BA/RnOv8xvgqtqpAX9IO4mRQxSx1Rlo4tqzeqb0sOlruaOy3dug==",
+ "peer": true
+ }
+ }
+ },
+ "text-table": {
+ "version": "0.2.0",
+ "resolved": "https://registry.npmjs.org/text-table/-/text-table-0.2.0.tgz",
+ "integrity": "sha512-N+8UisAXDGk8PFXP4HAzVR9nbfmVJ3zYLAWiTIoqC5v5isinhr+r5uaO8+7r3BMfuNIufIsA7RdpVgacC2cSpw=="
+ },
+ "to-fast-properties": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/to-fast-properties/-/to-fast-properties-2.0.0.tgz",
+ "integrity": "sha512-/OaKK0xYrs3DmxRYqL/yDc+FxFUVYhDlXMhRmv3z915w2HF1tnN1omB354j8VUGO/hbRzyD6Y3sA7v7GS/ceog=="
+ },
+ "to-regex-range": {
+ "version": "5.0.1",
+ "resolved": "https://registry.npmjs.org/to-regex-range/-/to-regex-range-5.0.1.tgz",
+ "integrity": "sha512-65P7iz6X5yEr1cwcgvQxbbIw7Uk3gOy5dIdtZ4rDveLqhrdJP+Li/Hx6tyK0NEb+2GCyneCMJiGqrADCSNk8sQ==",
+ "dev": true,
+ "requires": {
+ "is-number": "^7.0.0"
+ }
+ },
+ "type-check": {
+ "version": "0.4.0",
+ "resolved": "https://registry.npmjs.org/type-check/-/type-check-0.4.0.tgz",
+ "integrity": "sha512-XleUoc9uwGXqjWwXaUTZAmzMcFZ5858QA2vvx1Ur5xIcixXIP+8LnFDgRplU30us6teqdlskFfu+ae4K79Ooew==",
+ "requires": {
+ "prelude-ls": "^1.2.1"
+ }
+ },
+ "type-fest": {
+ "version": "0.20.2",
+ "resolved": "https://registry.npmjs.org/type-fest/-/type-fest-0.20.2.tgz",
+ "integrity": "sha512-Ne+eE4r0/iWnpAxD852z3A+N0Bt5RN//NjJwRd2VFHEmrywxf5vsZlh4R6lixl6B+wz/8d+maTSAkN1FIkI3LQ=="
+ },
+ "unbox-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/unbox-primitive/-/unbox-primitive-1.0.2.tgz",
+ "integrity": "sha512-61pPlCD9h51VoreyJ0BReideM3MDKMKnh6+V9L08331ipq6Q8OFXZYiqP6n/tbHx4s5I9uRhcye6BrbkizkBDw==",
+ "peer": true,
+ "requires": {
+ "call-bind": "^1.0.2",
+ "has-bigints": "^1.0.2",
+ "has-symbols": "^1.0.3",
+ "which-boxed-primitive": "^1.0.2"
+ }
+ },
+ "update-browserslist-db": {
+ "version": "1.0.10",
+ "resolved": "https://registry.npmjs.org/update-browserslist-db/-/update-browserslist-db-1.0.10.tgz",
+ "integrity": "sha512-OztqDenkfFkbSG+tRxBeAnCVPckDBcvibKd35yDONx6OU8N7sqgwc7rCbkJ/WcYtVRZ4ba68d6byhC21GFh7sQ==",
+ "requires": {
+ "escalade": "^3.1.1",
+ "picocolors": "^1.0.0"
+ }
+ },
+ "uri-js": {
+ "version": "4.4.1",
+ "resolved": "https://registry.npmjs.org/uri-js/-/uri-js-4.4.1.tgz",
+ "integrity": "sha512-7rKUyy33Q1yc98pQ1DAmLtwX109F7TIfWlW1Ydo8Wl1ii1SeHieeh0HHfPeL2fMXK6z0s8ecKs9frCuLJvndBg==",
+ "requires": {
+ "punycode": "^2.1.0"
+ }
+ },
+ "v8-compile-cache": {
+ "version": "2.3.0",
+ "resolved": "https://registry.npmjs.org/v8-compile-cache/-/v8-compile-cache-2.3.0.tgz",
+ "integrity": "sha512-l8lCEmLcLYZh4nbunNZvQCJc5pv7+RCwa8q/LdUx8u7lsWvPDKmpodJAJNwkAhJC//dFY48KuIEmjtd4RViDrA==",
+ "peer": true
+ },
+ "which": {
+ "version": "2.0.2",
+ "resolved": "https://registry.npmjs.org/which/-/which-2.0.2.tgz",
+ "integrity": "sha512-BLI3Tl1TW3Pvl70l3yq3Y64i+awpwXqsGBYWkkqMtnbXgrMD+yj7rhW0kuEDxzJaYXGjEW5ogapKNMEKNMjibA==",
+ "requires": {
+ "isexe": "^2.0.0"
+ }
+ },
+ "which-boxed-primitive": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/which-boxed-primitive/-/which-boxed-primitive-1.0.2.tgz",
+ "integrity": "sha512-bwZdv0AKLpplFY2KZRX6TvyuN7ojjr7lwkg6ml0roIy9YeuSr7JS372qlNW18UQYzgYK9ziGcerWqZOmEn9VNg==",
+ "peer": true,
+ "requires": {
+ "is-bigint": "^1.0.1",
+ "is-boolean-object": "^1.1.0",
+ "is-number-object": "^1.0.4",
+ "is-string": "^1.0.5",
+ "is-symbol": "^1.0.3"
+ }
+ },
+ "word-wrap": {
+ "version": "1.2.3",
+ "resolved": "https://registry.npmjs.org/word-wrap/-/word-wrap-1.2.3.tgz",
+ "integrity": "sha512-Hz/mrNwitNRh/HUAtM/VT/5VH+ygD6DV7mYKZAtHOrbs8U7lvPS6xf7EJKMF0uW1KJCl0H701g3ZGus+muE5vQ=="
+ },
+ "workerpool": {
+ "version": "6.2.1",
+ "resolved": "https://registry.npmjs.org/workerpool/-/workerpool-6.2.1.tgz",
+ "integrity": "sha512-ILEIE97kDZvF9Wb9f6h5aXK4swSlKGUcOEGiIYb2OOu/IrDU9iwj0fD//SsA6E5ibwJxpEvhullJY4Sl4GcpAw==",
+ "dev": true
+ },
+ "wrap-ansi": {
+ "version": "7.0.0",
+ "resolved": "https://registry.npmjs.org/wrap-ansi/-/wrap-ansi-7.0.0.tgz",
+ "integrity": "sha512-YVGIj2kamLSTxw6NsZjoBxfSwsn0ycdesmc4p+Q21c5zPuZ1pl+NfxVdxPtdHvmNVOQ6XSYG4AUtyt/Fi7D16Q==",
+ "dev": true,
+ "requires": {
+ "ansi-styles": "^4.0.0",
+ "string-width": "^4.1.0",
+ "strip-ansi": "^6.0.0"
+ },
+ "dependencies": {
+ "ansi-styles": {
+ "version": "4.3.0",
+ "resolved": "https://registry.npmjs.org/ansi-styles/-/ansi-styles-4.3.0.tgz",
+ "integrity": "sha512-zbB9rCJAT1rbjiVDb2hqKFHNYLxgtk8NURxZ3IZwD3F6NtxbXZQCnnSi1Lkx+IDohdPlFp222wVALIheZJQSEg==",
+ "dev": true,
+ "requires": {
+ "color-convert": "^2.0.1"
+ }
+ },
+ "color-convert": {
+ "version": "2.0.1",
+ "resolved": "https://registry.npmjs.org/color-convert/-/color-convert-2.0.1.tgz",
+ "integrity": "sha512-RRECPsj7iu/xb5oKYcsFHSppFNnsj/52OVTRKb4zP5onXwVF3zVmmToNcOfGC+CRDpfK/U584fMg38ZHCaElKQ==",
+ "dev": true,
+ "requires": {
+ "color-name": "~1.1.4"
+ }
+ },
+ "color-name": {
+ "version": "1.1.4",
+ "resolved": "https://registry.npmjs.org/color-name/-/color-name-1.1.4.tgz",
+ "integrity": "sha512-dOy+3AuW3a2wNbZHIuMZpTcgjGuLU/uBL/ubcZF9OXbDo8ff4O8yVp5Bf0efS8uEoYo5q4Fx7dY9OgQGXgAsQA==",
+ "dev": true
+ }
+ }
+ },
+ "wrappy": {
+ "version": "1.0.2",
+ "resolved": "https://registry.npmjs.org/wrappy/-/wrappy-1.0.2.tgz",
+ "integrity": "sha512-l4Sp/DRseor9wL6EvV2+TuQn63dMkPjZ/sp9XkghTEbV9KlPS1xUsZ3u7/IQO4wxtcFB4bgpQPRcR3QCvezPcQ=="
+ },
+ "y18n": {
+ "version": "5.0.8",
+ "resolved": "https://registry.npmjs.org/y18n/-/y18n-5.0.8.tgz",
+ "integrity": "sha512-0pfFzegeDWJHJIAmTLRP2DwHjdF5s7jo9tuztdQxAhINCdvS+3nGINqPd00AphqJR/0LhANUS6/+7SCb98YOfA==",
+ "dev": true
+ },
+ "yallist": {
+ "version": "4.0.0",
+ "resolved": "https://registry.npmjs.org/yallist/-/yallist-4.0.0.tgz",
+ "integrity": "sha512-3wdGidZyq5PB084XLES5TpOSRA3wjXAlIWMhum2kRcv/41Sn2emQ0dycQW4uZXLejwKvg6EsvbdlVL+FYEct7A==",
+ "peer": true
+ },
+ "yargs": {
+ "version": "16.2.0",
+ "resolved": "https://registry.npmjs.org/yargs/-/yargs-16.2.0.tgz",
+ "integrity": "sha512-D1mvvtDG0L5ft/jGWkLpG1+m0eQxOfaBvTNELraWj22wSVUMWxZUvYgJYcKh6jGGIkJFhH4IZPQhR4TKpc8mBw==",
+ "dev": true,
+ "requires": {
+ "cliui": "^7.0.2",
+ "escalade": "^3.1.1",
+ "get-caller-file": "^2.0.5",
+ "require-directory": "^2.1.1",
+ "string-width": "^4.2.0",
+ "y18n": "^5.0.5",
+ "yargs-parser": "^20.2.2"
+ }
+ },
+ "yargs-parser": {
+ "version": "20.2.4",
+ "resolved": "https://registry.npmjs.org/yargs-parser/-/yargs-parser-20.2.4.tgz",
+ "integrity": "sha512-WOkpgNhPTlE73h4VFAFsOnomJVaovO8VqLDzy5saChRBFQFBoMYirowyW+Q9HB4HFF4Z7VZTiG3iSzJJA29yRA==",
+ "dev": true
+ },
+ "yargs-unparser": {
+ "version": "2.0.0",
+ "resolved": "https://registry.npmjs.org/yargs-unparser/-/yargs-unparser-2.0.0.tgz",
+ "integrity": "sha512-7pRTIA9Qc1caZ0bZ6RYRGbHJthJWuakf+WmHK0rVeLkNrrGhfoabBNdue6kdINI6r4if7ocq9aD/n7xwKOdzOA==",
+ "dev": true,
+ "requires": {
+ "camelcase": "^6.0.0",
+ "decamelize": "^4.0.0",
+ "flat": "^5.0.2",
+ "is-plain-obj": "^2.1.0"
+ }
+ },
+ "yocto-queue": {
+ "version": "0.1.0",
+ "resolved": "https://registry.npmjs.org/yocto-queue/-/yocto-queue-0.1.0.tgz",
+ "integrity": "sha512-rVksvsnNCdJ/ohGc6xgPwyN8eheCxsiLM8mxuE/t/mOVqJewPuO1miLpTHQiRgTKCLexL4MeAFVagts7HmNZ2Q=="
+ }
+ }
+}
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/package.json b/tools/lint/eslint/eslint-plugin-mozilla/package.json
new file mode 100644
index 0000000000..39c06304dc
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/package.json
@@ -0,0 +1,55 @@
+{
+ "name": "eslint-plugin-mozilla",
+ "version": "3.0.1",
+ "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla project.",
+ "keywords": [
+ "eslint",
+ "eslintplugin",
+ "eslint-plugin",
+ "mozilla",
+ "firefox"
+ ],
+ "bugs": {
+ "url": "https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=Lint"
+ },
+ "homepage": "http://firefox-source-docs.mozilla.org/tools/lint/linters/eslint-plugin-mozilla.html",
+ "repository": {
+ "type": "hg",
+ "url": "https://hg.mozilla.org/mozilla-central/"
+ },
+ "author": "Mike Ratcliffe",
+ "main": "lib/index.js",
+ "dependencies": {
+ "@babel/core": "^7.20.5",
+ "@babel/eslint-parser": "^7.19.1",
+ "eslint-scope": "^7.1.1",
+ "eslint-visitor-keys": "^3.3.0",
+ "estraverse": "^5.3.0",
+ "htmlparser2": "^8.0.1",
+ "multi-ini": "^2.3.2"
+ },
+ "devDependencies": {
+ "eslint": "8.29.0",
+ "mocha": "10.1.0"
+ },
+ "peerDependencies": {
+ "@microsoft/eslint-plugin-sdl": "github:mozfreddyb/eslint-plugin-sdl#17b22cd527682108af7a1a4edacf69cb7a9b4a06",
+ "eslint": "^7.23.0 || ^8.0.0",
+ "eslint-config-prettier": "^8.0.0",
+ "eslint-plugin-fetch-options": "^0.0.5",
+ "eslint-plugin-html": "^7.0.0",
+ "eslint-plugin-no-unsanitized": "^4.0.0",
+ "eslint-plugin-prettier": "^3.0.0",
+ "prettier": "^1.19.1"
+ },
+ "engines": {
+ "node": ">=14.0.0"
+ },
+ "scripts": {
+ "prepack": "node scripts/createExports.js",
+ "test": "mocha --reporter 'reporters/mozilla-format.js' tests",
+ "postpublish": "rm -f lib/environments/saved-globals.json lib/rules/saved-rules-data.json",
+ "update-tooltool": "./update.sh"
+ },
+ "license": "MPL-2.0"
+}
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/reporters/mozilla-format.js b/tools/lint/eslint/eslint-plugin-mozilla/reporters/mozilla-format.js
new file mode 100644
index 0000000000..175fd8873e
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/reporters/mozilla-format.js
@@ -0,0 +1,57 @@
+/* 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/. */
+
+/**
+ * This file outputs the format that treeherder requires. If we integrate
+ * these tests with ./mach, then we may replace this with a json handler within
+ * mach itself.
+ */
+
+"use strict";
+
+var mocha = require("mocha");
+var path = require("path");
+module.exports = MozillaFormatter;
+
+function MozillaFormatter(runner) {
+ mocha.reporters.Base.call(this, runner);
+ var passes = 0;
+ var failures = [];
+
+ runner.on("start", () => {
+ console.log("SUITE-START | eslint-plugin-mozilla");
+ });
+
+ runner.on("pass", function(test) {
+ passes++;
+ let title = test.title.replace(/\n/g, "|");
+ console.log(`TEST-PASS | ${path.basename(test.file)} | ${title}`);
+ });
+
+ runner.on("fail", function(test, err) {
+ failures.push(test);
+ // Replace any newlines in the title.
+ let title = test.title.replace(/\n/g, "|");
+ console.log(
+ `TEST-UNEXPECTED-FAIL | ${path.basename(test.file)} | ${title} | ${
+ err.message
+ }`
+ );
+ mocha.reporters.Base.list([test]);
+ });
+
+ runner.on("end", function() {
+ // Space the results out visually with an additional blank line.
+ console.log("");
+ console.log("INFO | Result summary:");
+ console.log(`INFO | Passed: ${passes}`);
+ console.log(`INFO | Failed: ${failures.length}`);
+ console.log("SUITE-END");
+ // Space the failures out visually with an additional blank line.
+ console.log("");
+ console.log("Failure summary:");
+ mocha.reporters.Base.list(failures);
+ process.exit(failures.length);
+ });
+}
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/scripts/createExports.js b/tools/lint/eslint/eslint-plugin-mozilla/scripts/createExports.js
new file mode 100644
index 0000000000..7f839edfba
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/scripts/createExports.js
@@ -0,0 +1,77 @@
+/**
+ * @fileoverview A script to export the known globals to a file.
+ * 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/.
+ */
+"use strict";
+
+var fs = require("fs");
+var path = require("path");
+var helpers = require("../lib/helpers");
+
+const eslintDir = path.join(helpers.rootDir, "tools", "lint", "eslint");
+
+const globalsFile = path.join(
+ eslintDir,
+ "eslint-plugin-mozilla",
+ "lib",
+ "environments",
+ "saved-globals.json"
+);
+const rulesFile = path.join(
+ eslintDir,
+ "eslint-plugin-mozilla",
+ "lib",
+ "rules",
+ "saved-rules-data.json"
+);
+
+console.log("Copying services.json");
+
+const env = helpers.getBuildEnvironment();
+
+const servicesFile = path.join(
+ env.topobjdir,
+ "xpcom",
+ "components",
+ "services.json"
+);
+const shipServicesFile = path.join(
+ eslintDir,
+ "eslint-plugin-mozilla",
+ "lib",
+ "services.json"
+);
+
+fs.writeFileSync(shipServicesFile, fs.readFileSync(servicesFile));
+
+console.log("Generating globals file");
+
+// Export the environments.
+let environmentGlobals = require("../lib/index.js").environments;
+
+return fs.writeFile(
+ globalsFile,
+ JSON.stringify({ environments: environmentGlobals }),
+ err => {
+ if (err) {
+ console.error(err);
+ process.exit(1);
+ }
+
+ console.log("Globals file generation complete");
+
+ console.log("Creating rules data file");
+ let rulesData = {};
+
+ return fs.writeFile(rulesFile, JSON.stringify({ rulesData }), err1 => {
+ if (err1) {
+ console.error(err1);
+ process.exit(1);
+ }
+
+ console.log("Globals file generation complete");
+ });
+ }
+);
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-Date-timing.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-Date-timing.js
new file mode 100644
index 0000000000..597961e8cc
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-Date-timing.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/avoid-Date-timing");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, type, message) {
+ return { code, errors: [{ message, type }] };
+}
+
+ruleTester.run("avoid-Date-timing", rule, {
+ valid: [
+ "new Date('2017-07-11');",
+ "new Date(1499790192440);",
+ "new Date(2017, 7, 11);",
+ "Date.UTC(2017, 7);",
+ ],
+ invalid: [
+ invalidCode(
+ "Date.now();",
+ "CallExpression",
+ "use performance.now() instead of Date.now() for timing measurements"
+ ),
+ invalidCode(
+ "new Date();",
+ "NewExpression",
+ "use performance.now() instead of new Date() for timing measurements"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-removeChild.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-removeChild.js
new file mode 100644
index 0000000000..e6a146898d
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/avoid-removeChild.js
@@ -0,0 +1,46 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/avoid-removeChild");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, message) {
+ if (!message) {
+ message =
+ "use element.remove() instead of " +
+ "element.parentNode.removeChild(element)";
+ }
+ return { code, errors: [{ message, type: "CallExpression" }] };
+}
+
+ruleTester.run("avoid-removeChild", rule, {
+ valid: [
+ "elt.remove();",
+ "elt.parentNode.parentNode.removeChild(elt2.parentNode);",
+ "elt.parentNode.removeChild(elt2);",
+ "elt.removeChild(elt2);",
+ ],
+ invalid: [
+ invalidCode("elt.parentNode.removeChild(elt);"),
+ invalidCode("elt.parentNode.parentNode.removeChild(elt.parentNode);"),
+ invalidCode("$(e).parentNode.removeChild($(e));"),
+ invalidCode("$('e').parentNode.removeChild($('e'));"),
+ invalidCode(
+ "elt.removeChild(elt.firstChild);",
+ "use element.firstChild.remove() instead of " +
+ "element.removeChild(element.firstChild)"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-listeners.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-listeners.js
new file mode 100644
index 0000000000..ca84e3474f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-listeners.js
@@ -0,0 +1,89 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/balanced-listeners");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function error(code, message) {
+ return {
+ code,
+ errors: [{ message, type: "Identifier" }],
+ };
+}
+
+ruleTester.run("balanced-listeners", rule, {
+ valid: [
+ "elt.addEventListener('event', handler);" +
+ "elt.removeEventListener('event', handler);",
+
+ "elt.addEventListener('event', handler, true);" +
+ "elt.removeEventListener('event', handler, true);",
+
+ "elt.addEventListener('event', handler, false);" +
+ "elt.removeEventListener('event', handler, false);",
+
+ "elt.addEventListener('event', handler);" +
+ "elt.removeEventListener('event', handler, false);",
+
+ "elt.addEventListener('event', handler, false);" +
+ "elt.removeEventListener('event', handler);",
+
+ "elt.addEventListener('event', handler, {capture: false});" +
+ "elt.removeEventListener('event', handler);",
+
+ "elt.addEventListener('event', handler);" +
+ "elt.removeEventListener('event', handler, {capture: false});",
+
+ "elt.addEventListener('event', handler, {capture: true});" +
+ "elt.removeEventListener('event', handler, true);",
+
+ "elt.addEventListener('event', handler, true);" +
+ "elt.removeEventListener('event', handler, {capture: true});",
+
+ "elt.addEventListener('event', handler, {once: true});",
+
+ "elt.addEventListener('event', handler, {once: true, capture: true});",
+ ],
+ invalid: [
+ error(
+ "elt.addEventListener('click', handler, false);",
+ "No corresponding 'removeEventListener(click)' was found."
+ ),
+
+ error(
+ "elt.addEventListener('click', handler, false);" +
+ "elt.removeEventListener('click', handler, true);",
+ "No corresponding 'removeEventListener(click)' was found."
+ ),
+
+ error(
+ "elt.addEventListener('click', handler, {capture: false});" +
+ "elt.removeEventListener('click', handler, true);",
+ "No corresponding 'removeEventListener(click)' was found."
+ ),
+
+ error(
+ "elt.addEventListener('click', handler, {capture: true});" +
+ "elt.removeEventListener('click', handler);",
+ "No corresponding 'removeEventListener(click)' was found."
+ ),
+
+ error(
+ "elt.addEventListener('click', handler, true);" +
+ "elt.removeEventListener('click', handler);",
+ "No corresponding 'removeEventListener(click)' was found."
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-observers.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-observers.js
new file mode 100644
index 0000000000..e5f31fa969
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/balanced-observers.js
@@ -0,0 +1,74 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/balanced-observers");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function error(code, message) {
+ return {
+ code,
+ errors: [{ message, type: "Identifier" }],
+ };
+}
+
+ruleTester.run("balanced-observers", rule, {
+ valid: [
+ "Services.obs.addObserver(observer, 'observable');" +
+ "Services.obs.removeObserver(observer, 'observable');",
+ "Services.prefs.addObserver('preference.name', otherObserver);" +
+ "Services.prefs.removeObserver('preference.name', otherObserver);",
+ ],
+ invalid: [
+ error(
+ // missing Services.obs.removeObserver
+ "Services.obs.addObserver(observer, 'observable');",
+ "No corresponding 'removeObserver(\"observable\")' was found."
+ ),
+
+ error(
+ // wrong observable name for Services.obs.removeObserver
+ "Services.obs.addObserver(observer, 'observable');" +
+ "Services.obs.removeObserver(observer, 'different-observable');",
+ "No corresponding 'removeObserver(\"observable\")' was found."
+ ),
+
+ error(
+ // missing Services.prefs.removeObserver
+ "Services.prefs.addObserver('preference.name', otherObserver);",
+ "No corresponding 'removeObserver(\"preference.name\")' was found."
+ ),
+
+ error(
+ // wrong observable name for Services.prefs.removeObserver
+ "Services.prefs.addObserver('preference.name', otherObserver);" +
+ "Services.prefs.removeObserver('other.preference', otherObserver);",
+ "No corresponding 'removeObserver(\"preference.name\")' was found."
+ ),
+
+ error(
+ // mismatch Services.prefs vs Services.obs
+ "Services.obs.addObserver(observer, 'observable');" +
+ "Services.prefs.removeObserver(observer, 'observable');",
+ "No corresponding 'removeObserver(\"observable\")' was found."
+ ),
+
+ error(
+ "Services.prefs.addObserver('preference.name', otherObserver);" +
+ // mismatch Services.prefs vs Services.obs
+ "Services.obs.removeObserver('preference.name', otherObserver);",
+ "No corresponding 'removeObserver(\"preference.name\")' was found."
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/consistent-if-bracing.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/consistent-if-bracing.js
new file mode 100644
index 0000000000..b6b5b849b9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/consistent-if-bracing.js
@@ -0,0 +1,40 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/consistent-if-bracing");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ return { code, errors: [{ messageId: "consistentIfBracing" }] };
+}
+
+ruleTester.run("consistent-if-bracing", rule, {
+ valid: [
+ "if (true) {1} else {0}",
+ "if (false) 1; else 0",
+ "if (true) {1} else if (true) {2} else {0}",
+ "if (true) {1} else if (true) {2} else if (true) {3} else {0}",
+ ],
+ invalid: [
+ invalidCode(`if (true) {1} else 0`),
+ invalidCode("if (true) 1; else {0}"),
+ invalidCode("if (true) {1} else if (true) 2; else {0}"),
+ invalidCode("if (true) 1; else if (true) {2} else {0}"),
+ invalidCode("if (true) {1} else if (true) 2; else {0}"),
+ invalidCode("if (true) {1} else if (true) {2} else 0"),
+ invalidCode("if (true) {1} else if (true) {2} else if (true) 3; else {0}"),
+ invalidCode("if (true) {if (true) 1; else {0}} else {0}"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/globals.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/globals.js
new file mode 100644
index 0000000000..80fa35f4a6
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/globals.js
@@ -0,0 +1,161 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+var { getGlobalsForCode } = require("../lib/globals");
+var assert = require("assert");
+
+/* global describe, it */
+
+describe("globals", function() {
+ it("should reflect top-level this property assignment", function() {
+ const globals = getGlobalsForCode(`
+this.foo = 10;
+`);
+ assert.deepEqual(globals, [{ name: "foo", writable: true }]);
+ });
+
+ it("should reflect this property assignment inside block", function() {
+ const globals = getGlobalsForCode(`
+{
+ this.foo = 10;
+}
+`);
+ assert.deepEqual(globals, [{ name: "foo", writable: true }]);
+ });
+
+ it("should ignore this property assignment inside function declaration", function() {
+ const globals = getGlobalsForCode(`
+function f() {
+ this.foo = 10;
+}
+`);
+ assert.deepEqual(globals, [{ name: "f", writable: true }]);
+ });
+
+ it("should ignore this property assignment inside function expression", function() {
+ const globals = getGlobalsForCode(`
+(function f() {
+ this.foo = 10;
+});
+`);
+ assert.deepEqual(globals, []);
+ });
+
+ it("should ignore this property assignment inside method", function() {
+ const globals = getGlobalsForCode(`
+({
+ method() {
+ this.foo = 10;
+ }
+});
+`);
+ assert.deepEqual(globals, []);
+ });
+
+ it("should ignore this property assignment inside accessor", function() {
+ const globals = getGlobalsForCode(`
+({
+ get x() {
+ this.foo = 10;
+ },
+ set x(v) {
+ this.bar = 10;
+ }
+});
+`);
+ assert.deepEqual(globals, []);
+ });
+
+ it("should reflect this property assignment inside arrow function", function() {
+ const globals = getGlobalsForCode(`
+() => {
+ this.foo = 10;
+};
+`);
+ assert.deepEqual(globals, [{ name: "foo", writable: true }]);
+ });
+
+ it("should ignore this property assignment inside arrow function inside function expression", function() {
+ const globals = getGlobalsForCode(`
+(function f() {
+ () => {
+ this.foo = 10;
+ };
+});
+`);
+ assert.deepEqual(globals, []);
+ });
+
+ it("should ignore this property assignment inside class static", function() {
+ const globals = getGlobalsForCode(`
+class A {
+ static {
+ this.foo = 10;
+ (() => {
+ this.bar = 10;
+ })();
+ }
+}
+`);
+ assert.deepEqual(globals, [{ name: "A", writable: true }]);
+ });
+
+ it("should ignore this property assignment inside class property", function() {
+ const globals = getGlobalsForCode(`
+class A {
+ a = this.foo = 10;
+ b = (() => {
+ this.bar = 10;
+ })();
+}
+`);
+ assert.deepEqual(globals, [{ name: "A", writable: true }]);
+ });
+
+ it("should ignore this property assignment inside class static property", function() {
+ const globals = getGlobalsForCode(`
+class A {
+ static a = this.foo = 10;
+ static b = (() => {
+ this.bar = 10;
+ })();
+}
+`);
+ assert.deepEqual(globals, [{ name: "A", writable: true }]);
+ });
+
+ it("should ignore this property assignment inside class private property", function() {
+ const globals = getGlobalsForCode(`
+class A {
+ #a = this.foo = 10;
+ #b = (() => {
+ this.bar = 10;
+ })();
+}
+`);
+ assert.deepEqual(globals, [{ name: "A", writable: true }]);
+ });
+
+ it("should ignore this property assignment inside class static private property", function() {
+ const globals = getGlobalsForCode(`
+class A {
+ static #a = this.foo = 10;
+ static #b = (() => {
+ this.bar = 10;
+ })();
+}
+`);
+ assert.deepEqual(globals, [{ name: "A", writable: true }]);
+ });
+
+ it("should reflect lazy getters", function() {
+ const globals = getGlobalsForCode(`
+ChromeUtils.defineESModuleGetters(this, {
+ A: "B",
+});
+`);
+ assert.deepEqual(globals, [{ name: "A", writable: true, explicit: true }]);
+ });
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/lazy-getter-object-name.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/lazy-getter-object-name.js
new file mode 100644
index 0000000000..8a75e6238c
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/lazy-getter-object-name.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/lazy-getter-object-name");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester();
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ let message =
+ "The variable name of the object passed to ChromeUtils.defineESModuleGetters must be `lazy`";
+ return { code, errors: [{ message, type: "CallExpression" }] };
+}
+
+ruleTester.run("lazy-getter-object-name", rule, {
+ valid: [
+ `
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`,
+ ],
+ invalid: [
+ invalidCode(`
+ ChromeUtils.defineESModuleGetters(obj, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`),
+ invalidCode(`
+ ChromeUtils.defineESModuleGetters(this, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`),
+ invalidCode(`
+ ChromeUtils.defineESModuleGetters(window, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/mark-exported-symbols-as-used.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/mark-exported-symbols-as-used.js
new file mode 100644
index 0000000000..d004650997
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/mark-exported-symbols-as-used.js
@@ -0,0 +1,45 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/mark-exported-symbols-as-used");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, type, message) {
+ return { code, errors: [{ message, type }] };
+}
+
+ruleTester.run("mark-exported-symbols-as-used", rule, {
+ valid: [
+ "var EXPORTED_SYMBOLS = ['foo'];",
+ "this.EXPORTED_SYMBOLS = ['foo'];",
+ ],
+ invalid: [
+ invalidCode(
+ "let EXPORTED_SYMBOLS = ['foo'];",
+ "VariableDeclaration",
+ "EXPORTED_SYMBOLS cannot be declared via `let`. Use `var` or `this.EXPORTED_SYMBOLS =`"
+ ),
+ invalidCode(
+ "var EXPORTED_SYMBOLS = 'foo';",
+ "VariableDeclaration",
+ "Unexpected assignment of non-Array to EXPORTED_SYMBOLS"
+ ),
+ invalidCode(
+ "this.EXPORTED_SYMBOLS = 'foo';",
+ "AssignmentExpression",
+ "Unexpected assignment of non-Array to EXPORTED_SYMBOLS"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js
new file mode 100644
index 0000000000..b8275a00ef
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-addtask-setup.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-addtask-setup");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 9 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function callError(message) {
+ return [{ message, type: "CallExpression" }];
+}
+
+ruleTester.run("no-addtask-setup", rule, {
+ valid: [
+ "add_task(function() {});",
+ "add_task(function () {});",
+ "add_task(function foo() {});",
+ "add_task(async function() {});",
+ "add_task(async function () {});",
+ "add_task(async function foo() {});",
+ "something(function setup() {});",
+ "something(async function setup() {});",
+ "add_task(setup);",
+ "add_task(setup => {});",
+ "add_task(async setup => {});",
+ ],
+ invalid: [
+ {
+ code: "add_task(function setup() {});",
+ output: "add_setup(function() {});",
+ errors: callError(
+ "Do not use add_task() for setup, use add_setup() instead."
+ ),
+ },
+ {
+ code: "add_task(function setup () {});",
+ output: "add_setup(function () {});",
+ errors: callError(
+ "Do not use add_task() for setup, use add_setup() instead."
+ ),
+ },
+ {
+ code: "add_task(async function setup() {});",
+ output: "add_setup(async function() {});",
+ errors: callError(
+ "Do not use add_task() for setup, use add_setup() instead."
+ ),
+ },
+ {
+ code: "add_task(async function setup () {});",
+ output: "add_setup(async function () {});",
+ errors: callError(
+ "Do not use add_task() for setup, use add_setup() instead."
+ ),
+ },
+ {
+ code: "add_task(async function setUp() {});",
+ output: "add_setup(async function() {});",
+ errors: callError(
+ "Do not use add_task() for setup, use add_setup() instead."
+ ),
+ },
+ {
+ code: "add_task(async function init() {});",
+ output: "add_setup(async function() {});",
+ errors: callError(
+ "Do not use add_task() for setup, use add_setup() instead."
+ ),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-arbitrary-setTimeout.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-arbitrary-setTimeout.js
new file mode 100644
index 0000000000..907b439b3c
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-arbitrary-setTimeout.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-arbitrary-setTimeout");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function wrapCode(code, filename = "xpcshell/test_foo.js") {
+ return { code, filename };
+}
+
+function invalidCode(code) {
+ let message =
+ "listen for events instead of setTimeout() with arbitrary delay";
+ let obj = wrapCode(code);
+ obj.errors = [{ message, type: "CallExpression" }];
+ return obj;
+}
+
+ruleTester.run("no-arbitrary-setTimeout", rule, {
+ valid: [
+ wrapCode("setTimeout(function() {}, 0);"),
+ wrapCode("setTimeout(function() {});"),
+ wrapCode("setTimeout(function() {}, 10);", "test_foo.js"),
+ ],
+ invalid: [
+ invalidCode("setTimeout(function() {}, 10);"),
+ invalidCode("setTimeout(function() {}, timeout);"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-compare-against-boolean-literals.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-compare-against-boolean-literals.js
new file mode 100644
index 0000000000..722e6b5dda
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-compare-against-boolean-literals.js
@@ -0,0 +1,61 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-compare-against-boolean-literals");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function callError(message) {
+ return [{ message, type: "BinaryExpression" }];
+}
+
+const MESSAGE = "Don't compare for inexact equality against boolean literals";
+
+ruleTester.run("no-compare-against-boolean-literals", rule, {
+ valid: [`if (!foo) {}`, `if (!!foo) {}`],
+ invalid: [
+ {
+ code: `if (foo == true) {}`,
+ errors: callError(MESSAGE),
+ },
+ {
+ code: `if (foo != true) {}`,
+ errors: callError(MESSAGE),
+ },
+ {
+ code: `if (foo == false) {}`,
+ errors: callError(MESSAGE),
+ },
+ {
+ code: `if (foo != false) {}`,
+ errors: callError(MESSAGE),
+ },
+ {
+ code: `if (true == foo) {}`,
+ errors: callError(MESSAGE),
+ },
+ {
+ code: `if (true != foo) {}`,
+ errors: callError(MESSAGE),
+ },
+ {
+ code: `if (false == foo) {}`,
+ errors: callError(MESSAGE),
+ },
+ {
+ code: `if (false != foo) {}`,
+ errors: callError(MESSAGE),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-cu-reportError.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-cu-reportError.js
new file mode 100644
index 0000000000..16aa8b6358
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-cu-reportError.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-cu-reportError");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 9 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function callError() {
+ return [{ messageId: "useConsoleError", type: "CallExpression" }];
+}
+
+ruleTester.run("no-cu-reportError", rule, {
+ valid: [
+ "console.error('foo')",
+ "Cu.cloneInto({}, {})",
+ "foo().catch(console.error)",
+ // TODO: Bug 1802347 - this should be treated as an error as well.
+ "Cu.reportError('foo', stack)",
+ ],
+ invalid: [
+ {
+ code: "Cu.reportError('foo')",
+ output: "console.error('foo')",
+ errors: callError(),
+ },
+ {
+ code: "Cu.reportError(bar)",
+ output: "console.error(bar)",
+ errors: callError(),
+ },
+ {
+ code: "foo().catch(Cu.reportError)",
+ output: "foo().catch(console.error)",
+ errors: callError(),
+ },
+ // When referencing identifiers/members, try to reference them rather
+ // than stringifying:
+ {
+ code: "Cu.reportError('foo' + e)",
+ output: "console.error('foo', e)",
+ errors: callError(),
+ },
+ {
+ code: "Cu.reportError('foo' + msg.data)",
+ output: "console.error('foo', msg.data)",
+ errors: callError(),
+ },
+ // Don't touch existing concatenation of literals (usually done for
+ // wrapping reasons)
+ {
+ code: "Cu.reportError('foo' + 'bar' + 'baz')",
+ output: "console.error('foo' + 'bar' + 'baz')",
+ errors: callError(),
+ },
+ // Ensure things work when nested:
+ {
+ code: "Cu.reportError('foo' + e + 'baz')",
+ output: "console.error('foo', e, 'baz')",
+ errors: callError(),
+ },
+ // Ensure things work when nested in different places:
+ {
+ code: "Cu.reportError('foo' + e + 'quux' + 'baz')",
+ output: "console.error('foo', e, 'quux' + 'baz')",
+ errors: callError(),
+ },
+ {
+ code: "Cu.reportError('foo' + 'quux' + e + 'baz')",
+ output: "console.error('foo' + 'quux', e, 'baz')",
+ errors: callError(),
+ },
+ // Ensure things work with parens changing order of operations:
+ {
+ code: "Cu.reportError('foo' + 'quux' + (e + 'baz'))",
+ output: "console.error('foo' + 'quux' + (e + 'baz'))",
+ errors: callError(),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-define-cc-etc.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-define-cc-etc.js
new file mode 100644
index 0000000000..735c7f4654
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-define-cc-etc.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-define-cc-etc");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 9 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, varNames) {
+ if (!Array.isArray(varNames)) {
+ varNames = [varNames];
+ }
+ return {
+ code,
+ errors: varNames.map(name => {
+ return {
+ message: `${name} is now defined in global scope, a separate definition is no longer necessary.`,
+ type: "VariableDeclarator",
+ };
+ }),
+ };
+}
+
+ruleTester.run("no-define-cc-etc", rule, {
+ valid: [
+ "var Cm = Components.manager;",
+ "const CC = Components.Constructor;",
+ "var {Constructor: CC, manager: Cm} = Components;",
+ "const {Constructor: CC, manager: Cm} = Components;",
+ "foo.Cc.test();",
+ "const {bar, ...foo} = obj;",
+ ],
+ invalid: [
+ invalidCode("var Cc;", "Cc"),
+ invalidCode("let Cc;", "Cc"),
+ invalidCode("let Ci;", "Ci"),
+ invalidCode("let Cr;", "Cr"),
+ invalidCode("let Cu;", "Cu"),
+ invalidCode("var Cc = Components.classes;", "Cc"),
+ invalidCode("const {classes: Cc} = Components;", "Cc"),
+ invalidCode("let {classes: Cc, manager: Cm} = Components", "Cc"),
+ invalidCode("const Cu = Components.utils;", "Cu"),
+ invalidCode("var Ci = Components.interfaces, Cc = Components.classes;", [
+ "Ci",
+ "Cc",
+ ]),
+ invalidCode("var {'interfaces': Ci, 'classes': Cc} = Components;", [
+ "Ci",
+ "Cc",
+ ]),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-throw-cr-literal.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-throw-cr-literal.js
new file mode 100644
index 0000000000..6750213e72
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-throw-cr-literal.js
@@ -0,0 +1,62 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-throw-cr-literal");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, output, messageId) {
+ return {
+ code,
+ output,
+ errors: [{ messageId, type: "ThrowStatement" }],
+ };
+}
+
+ruleTester.run("no-throw-cr-literal", rule, {
+ valid: [
+ 'throw Components.Exception("", Cr.NS_ERROR_NOT_IMPLEMENTED);',
+ 'throw Components.Exception("", Components.results.NS_ERROR_UNEXPECTED);',
+ 'function t() { throw Components.Exception("", Cr.NS_ERROR_NO_CONTENT); }',
+ // We don't handle combined values, regular no-throw-literal catches them
+ 'throw Components.results.NS_ERROR_UNEXPECTED + "whoops";',
+ ],
+ invalid: [
+ invalidCode(
+ "throw Cr.NS_ERROR_NO_INTERFACE;",
+ 'throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);',
+ "bareCR"
+ ),
+ invalidCode(
+ "throw Components.results.NS_ERROR_ABORT;",
+ 'throw Components.Exception("", Components.results.NS_ERROR_ABORT);',
+ "bareComponentsResults"
+ ),
+ invalidCode(
+ "function t() { throw Cr.NS_ERROR_NULL_POINTER; }",
+ 'function t() { throw Components.Exception("", Cr.NS_ERROR_NULL_POINTER); }',
+ "bareCR"
+ ),
+ invalidCode(
+ "throw new Error(Cr.NS_ERROR_ABORT);",
+ 'throw Components.Exception("", Cr.NS_ERROR_ABORT);',
+ "newErrorCR"
+ ),
+ invalidCode(
+ "throw new Error(Components.results.NS_ERROR_ABORT);",
+ 'throw Components.Exception("", Components.results.NS_ERROR_ABORT);',
+ "newErrorComponentsResults"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-parameters.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-parameters.js
new file mode 100644
index 0000000000..0c9b12f6eb
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-parameters.js
@@ -0,0 +1,147 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-useless-parameters");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function callError(message) {
+ return [{ message, type: "CallExpression" }];
+}
+
+ruleTester.run("no-useless-parameters", rule, {
+ valid: [
+ "Services.prefs.clearUserPref('browser.search.custom');",
+ "Services.removeObserver('notification name', {});",
+ "Services.io.newURI('http://example.com');",
+ "Services.io.newURI('http://example.com', 'utf8');",
+ "elt.addEventListener('click', handler);",
+ "elt.addEventListener('click', handler, true);",
+ "elt.addEventListener('click', handler, {once: true});",
+ "elt.removeEventListener('click', handler);",
+ "elt.removeEventListener('click', handler, true);",
+ "Services.obs.addObserver(this, 'topic', true);",
+ "Services.obs.addObserver(this, 'topic');",
+ "Services.prefs.addObserver('branch', this, true);",
+ "Services.prefs.addObserver('branch', this);",
+ "array.appendElement(elt);",
+ "Services.obs.notifyObservers(obj, 'topic', 'data');",
+ "Services.obs.notifyObservers(obj, 'topic');",
+ "window.getComputedStyle(elt);",
+ "window.getComputedStyle(elt, ':before');",
+ ],
+ invalid: [
+ {
+ code: "Services.prefs.clearUserPref('browser.search.custom', false);",
+ output: "Services.prefs.clearUserPref('browser.search.custom');",
+ errors: callError("clearUserPref takes only 1 parameter."),
+ },
+ {
+ code: "Services.prefs.clearUserPref('browser.search.custom',\n false);",
+ output: "Services.prefs.clearUserPref('browser.search.custom');",
+ errors: callError("clearUserPref takes only 1 parameter."),
+ },
+ {
+ code: "Services.removeObserver('notification name', {}, false);",
+ output: "Services.removeObserver('notification name', {});",
+ errors: callError("removeObserver only takes 2 parameters."),
+ },
+ {
+ code: "Services.removeObserver('notification name', {}, true);",
+ output: "Services.removeObserver('notification name', {});",
+ errors: callError("removeObserver only takes 2 parameters."),
+ },
+ {
+ code: "Services.io.newURI('http://example.com', null, null);",
+ output: "Services.io.newURI('http://example.com');",
+ errors: callError("newURI's last parameters are optional."),
+ },
+ {
+ code: "Services.io.newURI('http://example.com', 'utf8', null);",
+ output: "Services.io.newURI('http://example.com', 'utf8');",
+ errors: callError("newURI's last parameters are optional."),
+ },
+ {
+ code: "Services.io.newURI('http://example.com', null);",
+ output: "Services.io.newURI('http://example.com');",
+ errors: callError("newURI's last parameters are optional."),
+ },
+ {
+ code: "Services.io.newURI('http://example.com', '', '');",
+ output: "Services.io.newURI('http://example.com');",
+ errors: callError("newURI's last parameters are optional."),
+ },
+ {
+ code: "Services.io.newURI('http://example.com', '');",
+ output: "Services.io.newURI('http://example.com');",
+ errors: callError("newURI's last parameters are optional."),
+ },
+ {
+ code: "elt.addEventListener('click', handler, false);",
+ output: "elt.addEventListener('click', handler);",
+ errors: callError(
+ "addEventListener's third parameter can be omitted when it's false."
+ ),
+ },
+ {
+ code: "elt.removeEventListener('click', handler, false);",
+ output: "elt.removeEventListener('click', handler);",
+ errors: callError(
+ "removeEventListener's third parameter can be omitted when it's" +
+ " false."
+ ),
+ },
+ {
+ code: "Services.obs.addObserver(this, 'topic', false);",
+ output: "Services.obs.addObserver(this, 'topic');",
+ errors: callError(
+ "addObserver's third parameter can be omitted when it's false."
+ ),
+ },
+ {
+ code: "Services.prefs.addObserver('branch', this, false);",
+ output: "Services.prefs.addObserver('branch', this);",
+ errors: callError(
+ "addObserver's third parameter can be omitted when it's false."
+ ),
+ },
+ {
+ code: "array.appendElement(elt, false);",
+ output: "array.appendElement(elt);",
+ errors: callError(
+ "appendElement's second parameter can be omitted when it's false."
+ ),
+ },
+ {
+ code: "Services.obs.notifyObservers(obj, 'topic', null);",
+ output: "Services.obs.notifyObservers(obj, 'topic');",
+ errors: callError("notifyObservers's third parameter can be omitted."),
+ },
+ {
+ code: "Services.obs.notifyObservers(obj, 'topic', '');",
+ output: "Services.obs.notifyObservers(obj, 'topic');",
+ errors: callError("notifyObservers's third parameter can be omitted."),
+ },
+ {
+ code: "window.getComputedStyle(elt, null);",
+ output: "window.getComputedStyle(elt);",
+ errors: callError("getComputedStyle's second parameter can be omitted."),
+ },
+ {
+ code: "window.getComputedStyle(elt, '');",
+ output: "window.getComputedStyle(elt);",
+ errors: callError("getComputedStyle's second parameter can be omitted."),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-removeEventListener.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-removeEventListener.js
new file mode 100644
index 0000000000..9f59c78d05
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-removeEventListener.js
@@ -0,0 +1,99 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-useless-removeEventListener");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ let message =
+ "use {once: true} instead of removeEventListener " +
+ "as the first instruction of the listener";
+ return { code, errors: [{ message, type: "CallExpression" }] };
+}
+
+ruleTester.run("no-useless-removeEventListener", rule, {
+ valid: [
+ // Listeners that aren't a function are always valid.
+ "elt.addEventListener('click', handler);",
+ "elt.addEventListener('click', handler, true);",
+ "elt.addEventListener('click', handler, {once: true});",
+
+ // Should not fail on empty functions.
+ "elt.addEventListener('click', function() {});",
+
+ // Should not reject when removing a listener for another event.
+ "elt.addEventListener('click', function listener() {" +
+ " elt.removeEventListener('keypress', listener);" +
+ "});",
+
+ // Should not reject when there's another instruction before
+ // removeEventListener.
+ "elt.addEventListener('click', function listener() {" +
+ " elt.focus();" +
+ " elt.removeEventListener('click', listener);" +
+ "});",
+
+ // Should not reject when wantsUntrusted is true.
+ "elt.addEventListener('click', function listener() {" +
+ " elt.removeEventListener('click', listener);" +
+ "}, false, true);",
+
+ // Should not reject when there's a literal and a variable
+ "elt.addEventListener('click', function listener() {" +
+ " elt.removeEventListener(eventName, listener);" +
+ "});",
+
+ // Should not reject when there's 2 different variables
+ "elt.addEventListener(event1, function listener() {" +
+ " elt.removeEventListener(event2, listener);" +
+ "});",
+
+ // Should not fail if this is a different type of event listener function.
+ "myfunc.addEventListener(listener);",
+ ],
+ invalid: [
+ invalidCode(
+ "elt.addEventListener('click', function listener() {" +
+ " elt.removeEventListener('click', listener);" +
+ "});"
+ ),
+ invalidCode(
+ "elt.addEventListener('click', function listener() {" +
+ " elt.removeEventListener('click', listener, true);" +
+ "}, true);"
+ ),
+ invalidCode(
+ "elt.addEventListener('click', function listener() {" +
+ " elt.removeEventListener('click', listener);" +
+ "}, {once: true});"
+ ),
+ invalidCode(
+ "elt.addEventListener('click', function listener() {" +
+ " /* Comment */" +
+ " elt.removeEventListener('click', listener);" +
+ "});"
+ ),
+ invalidCode(
+ "elt.addEventListener('click', function() {" +
+ " elt.removeEventListener('click', arguments.callee);" +
+ "});"
+ ),
+ invalidCode(
+ "elt.addEventListener(eventName, function listener() {" +
+ " elt.removeEventListener(eventName, listener);" +
+ "});"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-run-test.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-run-test.js
new file mode 100644
index 0000000000..3541467143
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/no-useless-run-test.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/no-useless-run-test");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, output) {
+ let message =
+ "Useless run_test function - only contains run_next_test; whole function can be removed";
+ return { code, output, errors: [{ message, type: "FunctionDeclaration" }] };
+}
+
+ruleTester.run("no-useless-run-test", rule, {
+ valid: [
+ "function run_test() {}",
+ "function run_test() {let args = 1; run_next_test();}",
+ "function run_test() {fakeCall(); run_next_test();}",
+ ],
+ invalid: [
+ // Single-line case.
+ invalidCode("function run_test() { run_next_test(); }", ""),
+ // Multiple-line cases
+ invalidCode(
+ `
+function run_test() {
+ run_next_test();
+}`,
+ ``
+ ),
+ invalidCode(
+ `
+foo();
+function run_test() {
+ run_next_test();
+}
+`,
+ `
+foo();
+`
+ ),
+ invalidCode(
+ `
+foo();
+function run_test() {
+ run_next_test();
+}
+bar();
+`,
+ `
+foo();
+bar();
+`
+ ),
+ invalidCode(
+ `
+foo();
+
+function run_test() {
+ run_next_test();
+}
+
+bar();`,
+ `
+foo();
+
+bar();`
+ ),
+ invalidCode(
+ `
+foo();
+
+function run_test() {
+ run_next_test();
+}
+
+// A comment
+bar();
+`,
+ `
+foo();
+
+// A comment
+bar();
+`
+ ),
+ invalidCode(
+ `
+foo();
+
+/**
+ * A useful comment.
+ */
+function run_test() {
+ run_next_test();
+}
+
+// A comment
+bar();
+`,
+ `
+foo();
+
+/**
+ * A useful comment.
+ */
+
+// A comment
+bar();
+`
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-boolean-length-check.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-boolean-length-check.js
new file mode 100644
index 0000000000..daf3c6f3d9
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-boolean-length-check.js
@@ -0,0 +1,97 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/prefer-boolean-length-check");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidError() {
+ let message = "Prefer boolean length check";
+ return [{ message, type: "BinaryExpression" }];
+}
+
+ruleTester.run("check-length", rule, {
+ valid: [
+ "if (foo.length && foo.length) {}",
+ "if (!foo.length) {}",
+ "if (foo.value == 0) {}",
+ "if (foo.value > 0) {}",
+ "if (0 == foo.value) {}",
+ "if (0 < foo.value) {}",
+ "var a = !!foo.length",
+ "function bar() { return !!foo.length }",
+ ],
+ invalid: [
+ {
+ code: "if (foo.length == 0) {}",
+ output: "if (!foo.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "if (foo.length > 0) {}",
+ output: "if (foo.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "if (0 < foo.length) {}",
+ output: "if (foo.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "if (0 == foo.length) {}",
+ output: "if (!foo.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "if (foo && foo.length == 0) {}",
+ output: "if (foo && !foo.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "if (foo.bar.length == 0) {}",
+ output: "if (!foo.bar.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "var a = foo.length>0",
+ output: "var a = !!foo.length",
+ errors: invalidError(),
+ },
+ {
+ code: "function bar() { return foo.length>0 }",
+ output: "function bar() { return !!foo.length }",
+ errors: invalidError(),
+ },
+ {
+ code: "if (foo && bar.length>0) {}",
+ output: "if (foo && bar.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "while (foo && bar.length>0) {}",
+ output: "while (foo && bar.length) {}",
+ errors: invalidError(),
+ },
+ {
+ code: "x = y && bar.length > 0",
+ output: "x = y && !!bar.length",
+ errors: invalidError(),
+ },
+ {
+ code: "function bar() { return x && foo.length > 0}",
+ output: "function bar() { return x && !!foo.length}",
+ errors: invalidError(),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-formatValues.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-formatValues.js
new file mode 100644
index 0000000000..0883130f17
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/prefer-formatValues.js
@@ -0,0 +1,72 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/prefer-formatValues");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function error(line, column = undefined) {
+ return {
+ message:
+ "prefer to use a single document.l10n.formatValues call instead " +
+ "of multiple calls to document.l10n.formatValue or document.l10n.formatValues",
+ type: "CallExpression",
+ line,
+ column,
+ };
+}
+
+ruleTester.run("check-length", rule, {
+ valid: [
+ "document.l10n.formatValue('foobar');",
+ "document.l10n.formatValues(['foobar', 'foobaz']);",
+ `if (foo) {
+ document.l10n.formatValue('foobar');
+ } else {
+ document.l10n.formatValue('foobaz');
+ }`,
+ `document.l10n.formatValue('foobaz');
+ if (foo) {
+ document.l10n.formatValue('foobar');
+ }`,
+ `if (foo) {
+ document.l10n.formatValue('foobar');
+ }
+ document.l10n.formatValue('foobaz');`,
+ `if (foo) {
+ document.l10n.formatValue('foobar');
+ }
+ document.l10n.formatValues(['foobaz']);`,
+ ],
+ invalid: [
+ {
+ code: `document.l10n.formatValue('foobar');
+ document.l10n.formatValue('foobaz');`,
+ errors: [error(1, 1), error(2, 14)],
+ },
+ {
+ code: `document.l10n.formatValue('foobar');
+ if (foo) {
+ document.l10n.formatValue('foobiz');
+ }
+ document.l10n.formatValue('foobaz');`,
+ errors: [error(1, 1), error(5, 14)],
+ },
+ {
+ code: `document.l10n.formatValues('foobar');
+ document.l10n.formatValue('foobaz');`,
+ errors: [error(1, 1), error(2, 14)],
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-addtask-only.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-addtask-only.js
new file mode 100644
index 0000000000..196b47f9cb
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-addtask-only.js
@@ -0,0 +1,56 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-addtask-only");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidError(output) {
+ let message =
+ "add_task(...).only() not allowed - add an exception if this is intentional";
+ return [
+ {
+ message,
+ type: "CallExpression",
+ suggestions: [{ desc: "Remove only() call from task", output }],
+ },
+ ];
+}
+
+ruleTester.run("reject-addtask-only", rule, {
+ valid: [
+ "add_task(foo())",
+ "add_task(foo()).skip()",
+ "add_task(function() {})",
+ "add_task(function() {}).skip()",
+ ],
+ invalid: [
+ {
+ code: "add_task(foo()).only()",
+ errors: invalidError("add_task(foo())"),
+ },
+ {
+ code: "add_task(foo()).only(bar())",
+ errors: invalidError("add_task(foo())"),
+ },
+ {
+ code: "add_task(function() {}).only()",
+ errors: invalidError("add_task(function() {})"),
+ },
+ {
+ code: "add_task(function() {}).only(bar())",
+ errors: invalidError("add_task(function() {})"),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-chromeutils-import-params.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-chromeutils-import-params.js
new file mode 100644
index 0000000000..8d813b30d8
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-chromeutils-import-params.js
@@ -0,0 +1,67 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-chromeutils-import-params");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidError(suggested) {
+ return [
+ {
+ message: "ChromeUtils.import only takes one argument.",
+ type: "CallExpression",
+ suggestions: [
+ {
+ desc: "Remove the unnecessary parameters.",
+ output: suggested,
+ },
+ ],
+ },
+ ];
+}
+
+ruleTester.run("reject-chromeutils-import-params", rule, {
+ valid: ['ChromeUtils.import("resource://some/path/to/My.jsm")'],
+ invalid: [
+ {
+ code: 'ChromeUtils.import("resource://some/path/to/My.jsm", null)',
+ errors: invalidError(
+ `ChromeUtils.import("resource://some/path/to/My.jsm")`
+ ),
+ },
+ {
+ code: `
+ChromeUtils.import(
+ "resource://some/path/to/My.jsm",
+ null
+);`,
+ errors: invalidError(`
+ChromeUtils.import(
+ "resource://some/path/to/My.jsm"
+);`),
+ },
+ {
+ code: 'ChromeUtils.import("resource://some/path/to/My.jsm", this)',
+ errors: invalidError(
+ `ChromeUtils.import("resource://some/path/to/My.jsm")`
+ ),
+ },
+ {
+ code: 'ChromeUtils.import("resource://some/path/to/My.jsm", foo, bar)',
+ errors: invalidError(
+ `ChromeUtils.import("resource://some/path/to/My.jsm")`
+ ),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-eager-module-in-lazy-getter.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-eager-module-in-lazy-getter.js
new file mode 100644
index 0000000000..0df97883a8
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-eager-module-in-lazy-getter.js
@@ -0,0 +1,80 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-eager-module-in-lazy-getter");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester();
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, uri) {
+ return { code, errors: [{ messageId: "eagerModule", data: { uri } }] };
+}
+
+ruleTester.run("reject-eager-module-in-lazy-getter", rule, {
+ valid: [
+ `
+ XPCOMUtils.defineLazyModuleGetter(
+ lazy, "Integration", "resource://gre/modules/Integration.jsm"
+ );
+`,
+ `
+ ChromeUtils.defineModuleGetter(
+ lazy, "Integration", "resource://gre/modules/Integration.jsm"
+ );
+`,
+ `
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ Integration: "resource://gre/modules/Integration.jsm",
+ });
+`,
+ `
+ ChromeUtils.defineESModuleGetters(lazy, {
+ Integration: "resource://gre/modules/Integration.sys.mjs",
+ });
+`,
+ ],
+ invalid: [
+ invalidCode(
+ `
+ XPCOMUtils.defineLazyModuleGetter(
+ lazy, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`,
+ "resource://gre/modules/AppConstants.jsm"
+ ),
+ invalidCode(
+ `
+ ChromeUtils.defineModuleGetter(
+ lazy, "XPCOMUtils", "resource://gre/modules/XPCOMUtils.jsm"
+ );
+`,
+ "resource://gre/modules/XPCOMUtils.jsm"
+ ),
+ invalidCode(
+ `
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.jsm",
+ });
+`,
+ "resource://gre/modules/AppConstants.jsm"
+ ),
+ invalidCode(
+ `
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`,
+ "resource://gre/modules/AppConstants.sys.mjs"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-global-this.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-global-this.js
new file mode 100644
index 0000000000..44ee559580
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-global-this.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-global-this");
+var RuleTester = require("eslint").RuleTester;
+
+// class static block is available from ES2022 = 13.
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 13 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ let message = "JSM should not use the global this";
+ return { code, errors: [{ message, type: "ThisExpression" }] };
+}
+
+ruleTester.run("reject-top-level-await", rule, {
+ valid: [
+ "function f() { this; }",
+ "(function f() { this; });",
+ "({ foo() { this; } });",
+ "({ get foo() { this; } })",
+ "({ set foo(x) { this; } })",
+ "class X { foo() { this; } }",
+ "class X { get foo() { this; } }",
+ "class X { set foo(x) { this; } }",
+ "class X { static foo() { this; } }",
+ "class X { static get foo() { this; } }",
+ "class X { static set foo(x) { this; } }",
+ "class X { P = this; }",
+ "class X { #P = this; }",
+ "class X { static { this; } }",
+ ],
+ invalid: [
+ invalidCode("this;"),
+ invalidCode("() => this;"),
+
+ invalidCode("this.foo = 10;"),
+ invalidCode("ChromeUtils.defineModuleGetter(this, {});"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-globalThis-modification.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-globalThis-modification.js
new file mode 100644
index 0000000000..a0002ff654
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-globalThis-modification.js
@@ -0,0 +1,90 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-globalThis-modification");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester();
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCall(code) {
+ return {
+ code,
+ errors: [
+ {
+ message:
+ "`globalThis` shouldn't be passed to function that can modify it. `globalThis` is the shared global inside the system module, and properties defined on it is visible from all modules.",
+ type: "CallExpression",
+ },
+ ],
+ };
+}
+
+function invalidAssignment(code) {
+ return {
+ code,
+ errors: [
+ {
+ message:
+ "`globalThis` shouldn't be modified. `globalThis` is the shared global inside the system module, and properties defined on it is visible from all modules.",
+ type: "AssignmentExpression",
+ },
+ ],
+ };
+}
+
+ruleTester.run("reject-globalThis-modification", rule, {
+ valid: [
+ `var x = globalThis.Array;`,
+ `Array in globalThis;`,
+ `result.deserialize(globalThis)`,
+ ],
+ invalid: [
+ invalidAssignment(`
+ globalThis.foo = 10;
+`),
+ invalidCall(`
+ Object.defineProperty(globalThis, "foo", { value: 10 });
+`),
+ invalidCall(`
+ Object.defineProperties(globalThis, {
+ foo: { value: 10 },
+ });
+`),
+ invalidCall(`
+ Object.assign(globalThis, { foo: 10 });
+`),
+ invalidCall(`
+ ChromeUtils.defineModuleGetter(
+ globalThis, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`),
+ invalidCall(`
+ ChromeUtils.defineESMGetters(globalThis, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`),
+ invalidCall(`
+ XPCOMUtils.defineLazyModuleGetter(
+ globalThis, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`),
+ invalidCall(`
+ XPCOMUtils.defineLazyModuleGetters(globalThis, {
+ AppConstants: "resource://gre/modules/AppConstants.jsm",
+ });
+`),
+ invalidCall(`
+ someFunction(1, globalThis);
+`),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-import-system-module-from-non-system.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-import-system-module-from-non-system.js
new file mode 100644
index 0000000000..b78a3a195d
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-import-system-module-from-non-system.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-import-system-module-from-non-system");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({
+ parserOptions: { ecmaVersion: 13, sourceType: "module" },
+});
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+ruleTester.run("reject-import-system-module-from-non-system", rule, {
+ valid: [
+ {
+ code: `const { AppConstants } = ChromeUtils.importESM("resource://gre/modules/AppConstants.sys.mjs");`,
+ },
+ ],
+ invalid: [
+ {
+ code: `import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";`,
+ errors: [{ messageId: "rejectStaticImportSystemModuleFromNonSystem" }],
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-importGlobalProperties.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-importGlobalProperties.js
new file mode 100644
index 0000000000..c86d9676d5
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-importGlobalProperties.js
@@ -0,0 +1,87 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-importGlobalProperties");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+ruleTester.run("reject-importGlobalProperties", rule, {
+ valid: [
+ {
+ code: "Cu.something();",
+ },
+ {
+ options: ["allownonwebidl"],
+ code: "Cu.importGlobalProperties(['fetch'])",
+ },
+ {
+ options: ["allownonwebidl"],
+ code: "XPCOMUtils.defineLazyGlobalGetters(this, ['fetch'])",
+ },
+ {
+ options: ["allownonwebidl"],
+ code: "Cu.importGlobalProperties(['TextEncoder'])",
+ filename: "foo.sjs",
+ },
+ {
+ options: ["allownonwebidl"],
+ code: "XPCOMUtils.defineLazyGlobalGetters(this, ['TextEncoder'])",
+ filename: "foo.sjs",
+ },
+ ],
+ invalid: [
+ {
+ code: "Cu.importGlobalProperties(['fetch'])",
+ options: ["everything"],
+ errors: [{ messageId: "unexpectedCall" }],
+ },
+ {
+ code: "XPCOMUtils.defineLazyGlobalGetters(this, ['fetch'])",
+ options: ["everything"],
+ errors: [{ messageId: "unexpectedCall" }],
+ },
+ {
+ code: "Cu.importGlobalProperties(['TextEncoder'])",
+ options: ["everything"],
+ errors: [{ messageId: "unexpectedCall" }],
+ },
+ {
+ code: "XPCOMUtils.defineLazyGlobalGetters(this, ['TextEncoder'])",
+ options: ["everything"],
+ errors: [{ messageId: "unexpectedCall" }],
+ },
+ {
+ code: "Cu.importGlobalProperties(['TextEncoder'])",
+ options: ["allownonwebidl"],
+ errors: [{ messageId: "unexpectedCallCuWebIdl" }],
+ },
+ {
+ code: "XPCOMUtils.defineLazyGlobalGetters(this, ['TextEncoder'])",
+ options: ["allownonwebidl"],
+ errors: [{ messageId: "unexpectedCallXPCOMWebIdl" }],
+ },
+ {
+ options: ["allownonwebidl"],
+ code: "Cu.importGlobalProperties(['TextEncoder'])",
+ errors: [{ messageId: "unexpectedCallCuWebIdl" }],
+ filename: "foo.js",
+ },
+ {
+ options: ["allownonwebidl"],
+ code: "XPCOMUtils.defineLazyGlobalGetters(this, ['TextEncoder'])",
+ errors: [{ messageId: "unexpectedCallXPCOMWebIdl" }],
+ filename: "foo.js",
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-lazy-imports-into-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-lazy-imports-into-globals.js
new file mode 100644
index 0000000000..b3df33a87d
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-lazy-imports-into-globals.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-lazy-imports-into-globals");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({
+ parserOptions: { ecmaVersion: 13, sourceType: "module" },
+});
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ return { code, errors: [{ messageId: "rejectLazyImportsIntoGlobals" }] };
+}
+
+ruleTester.run("reject-lazy-imports-into-globals", rule, {
+ valid: [
+ `
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", () => {});
+ `,
+ ],
+ invalid: [
+ invalidCode(`XPCOMUtils.defineLazyGetter(globalThis, "foo", () => {});`),
+ invalidCode(`XPCOMUtils.defineLazyGetter(window, "foo", () => {});`),
+ invalidCode(
+ `XPCOMUtils.defineLazyModuleGetter(globalThis, "foo", "foo.jsm");`
+ ),
+ invalidCode(`XPCOMUtils.defineLazyModuleGetter(window, "foo", "foo.jsm");`),
+ invalidCode(
+ `XPCOMUtils.defineLazyPreferenceGetter(globalThis, "foo", "foo.bar");`
+ ),
+ invalidCode(
+ `XPCOMUtils.defineLazyServiceGetter(globalThis, "foo", "@foo", "nsIFoo");`
+ ),
+ invalidCode(`XPCOMUtils.defineLazyGlobalGetters(globalThis, {});`),
+ invalidCode(`XPCOMUtils.defineLazyGlobalGetters(window, {});`),
+ invalidCode(`XPCOMUtils.defineLazyModuleGetters(globalThis, {});`),
+ invalidCode(`ChromeUtils.defineESModuleGetters(window, {});`),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-mixing-eager-and-lazy.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-mixing-eager-and-lazy.js
new file mode 100644
index 0000000000..b317cf3377
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-mixing-eager-and-lazy.js
@@ -0,0 +1,124 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-mixing-eager-and-lazy");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({
+ parserOptions: { ecmaVersion: 13, sourceType: "module" },
+});
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, uri) {
+ return { code, errors: [{ messageId: "mixedEagerAndLazy", data: { uri } }] };
+}
+
+ruleTester.run("reject-mixing-eager-and-lazy", rule, {
+ valid: [
+ `
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+`,
+ `
+ ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs");
+`,
+ `
+ import{ AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+`,
+ `
+ XPCOMUtils.defineLazyModuleGetter(
+ lazy, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`,
+ `
+ ChromeUtils.defineModuleGetter(
+ lazy, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`,
+ `
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.jsm",
+ });
+`,
+ `
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`,
+ `
+ if (some_condition) {
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ }
+ XPCOMUtils.defineLazyModuleGetter(
+ lazy, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`,
+ `
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ XPCOMUtils.defineLazyModuleGetter(
+ sandbox, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`,
+ `
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ XPCOMUtils.defineLazyModuleGetters(sandbox, {
+ AppConstants: "resource://gre/modules/AppConstants.jsm",
+ });
+`,
+ ],
+ invalid: [
+ invalidCode(
+ `
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ XPCOMUtils.defineLazyModuleGetter(
+ lazy, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`,
+ "resource://gre/modules/AppConstants.jsm"
+ ),
+ invalidCode(
+ `
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ ChromeUtils.defineModuleGetter(
+ lazy, "AppConstants", "resource://gre/modules/AppConstants.jsm"
+ );
+`,
+ "resource://gre/modules/AppConstants.jsm"
+ ),
+ invalidCode(
+ `
+ ChromeUtils.import("resource://gre/modules/AppConstants.jsm");
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.jsm",
+ });
+`,
+ "resource://gre/modules/AppConstants.jsm"
+ ),
+ invalidCode(
+ `
+ ChromeUtils.importESModule("resource://gre/modules/AppConstants.sys.mjs");
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`,
+ "resource://gre/modules/AppConstants.sys.mjs"
+ ),
+ invalidCode(
+ `
+ import { AppConstants } from "resource://gre/modules/AppConstants.sys.mjs";
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+`,
+ "resource://gre/modules/AppConstants.sys.mjs"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-multiple-getters-calls.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-multiple-getters-calls.js
new file mode 100644
index 0000000000..fa6117d19f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-multiple-getters-calls.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-multiple-getters-calls");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester();
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ return { code, errors: [{ messageId: "rejectMultipleCalls" }] };
+}
+
+ruleTester.run("reject-multiple-getters-calls", rule, {
+ valid: [
+ `
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ });
+ `,
+ `
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+ ChromeUtils.defineESModuleGetters(window, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ });
+ `,
+ `
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+ if (cond) {
+ ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ });
+ }
+ `,
+ ],
+ invalid: [
+ invalidCode(`
+ ChromeUtils.defineESModuleGetters(lazy, {
+ AppConstants: "resource://gre/modules/AppConstants.sys.mjs",
+ });
+ ChromeUtils.defineESModuleGetters(lazy, {
+ PlacesUtils: "resource://gre/modules/PlacesUtils.sys.mjs",
+ });
+ `),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-osfile.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-osfile.js
new file mode 100644
index 0000000000..c8eb3f2f5f
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-osfile.js
@@ -0,0 +1,50 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-osfile");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidError(os, util) {
+ let message = `${os} is deprecated. You should use ${util} instead.`;
+ return [{ message, type: "MemberExpression" }];
+}
+
+ruleTester.run("reject-osfile", rule, {
+ valid: ["IOUtils.write()"],
+ invalid: [
+ {
+ code: "OS.File.write()",
+ errors: invalidError("OS.File", "IOUtils"),
+ },
+ {
+ code: "lazy.OS.File.write()",
+ errors: invalidError("OS.File", "IOUtils"),
+ },
+ ],
+});
+
+ruleTester.run("reject-osfile", rule, {
+ valid: ["PathUtils.join()"],
+ invalid: [
+ {
+ code: "OS.Path.join()",
+ errors: invalidError("OS.Path", "PathUtils"),
+ },
+ {
+ code: "lazy.OS.Path.join()",
+ errors: invalidError("OS.Path", "PathUtils"),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-relative-requires.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-relative-requires.js
new file mode 100644
index 0000000000..19f30559ec
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-relative-requires.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-relative-requires");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidError() {
+ let message = "relative paths are not allowed with require()";
+ return [{ message, type: "CallExpression" }];
+}
+
+ruleTester.run("reject-relative-requires", rule, {
+ valid: [
+ 'require("devtools/absolute/path")',
+ 'require("resource://gre/modules/SomeModule.jsm")',
+ 'loader.lazyRequireGetter(this, "path", "devtools/absolute/path", true)',
+ 'loader.lazyRequireGetter(this, "Path", "devtools/absolute/path")',
+ ],
+ invalid: [
+ {
+ code: 'require("./relative/path")',
+ errors: invalidError(),
+ },
+ {
+ code: 'require("../parent/folder/path")',
+ errors: invalidError(),
+ },
+ {
+ code: 'loader.lazyRequireGetter(this, "path", "./relative/path", true)',
+ errors: invalidError(),
+ },
+ {
+ code:
+ 'loader.lazyRequireGetter(this, "path", "../parent/folder/path", true)',
+ errors: invalidError(),
+ },
+ {
+ code: 'loader.lazyRequireGetter(this, "path", "./relative/path")',
+ errors: invalidError(),
+ },
+ {
+ code: 'loader.lazyRequireGetter(this, "path", "../parent/folder/path")',
+ errors: invalidError(),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-scriptableunicodeconverter.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-scriptableunicodeconverter.js
new file mode 100644
index 0000000000..04fb8057db
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-scriptableunicodeconverter.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-scriptableunicodeconverter");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidError() {
+ let message =
+ "Ci.nsIScriptableUnicodeConverter is deprecated. You should use TextEncoder or TextDecoder instead.";
+ return [{ message, type: "MemberExpression" }];
+}
+
+ruleTester.run("reject-scriptableunicodeconverter", rule, {
+ valid: ["TextEncoder", "TextDecoder"],
+ invalid: [
+ {
+ code: "Ci.nsIScriptableUnicodeConverter",
+ errors: invalidError(),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-some-requires.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-some-requires.js
new file mode 100644
index 0000000000..b56dd2cf96
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-some-requires.js
@@ -0,0 +1,49 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-some-requires");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function requirePathError(path) {
+ const message = `require(${path}) is not allowed`;
+ return [{ message, type: "CallExpression" }];
+}
+
+const DEVTOOLS_FORBIDDEN_PATH = "^(resource://)?devtools/forbidden";
+
+ruleTester.run("reject-some-requires", rule, {
+ valid: [
+ {
+ code: 'require("devtools/not-forbidden/path")',
+ options: [DEVTOOLS_FORBIDDEN_PATH],
+ },
+ {
+ code: 'require("resource://devtools/not-forbidden/path")',
+ options: [DEVTOOLS_FORBIDDEN_PATH],
+ },
+ ],
+ invalid: [
+ {
+ code: 'require("devtools/forbidden/path")',
+ errors: requirePathError("devtools/forbidden/path"),
+ options: [DEVTOOLS_FORBIDDEN_PATH],
+ },
+ {
+ code: 'require("resource://devtools/forbidden/path")',
+ errors: requirePathError("resource://devtools/forbidden/path"),
+ options: [DEVTOOLS_FORBIDDEN_PATH],
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-top-level-await.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-top-level-await.js
new file mode 100644
index 0000000000..844af78643
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/reject-top-level-await.js
@@ -0,0 +1,36 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/reject-top-level-await");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({
+ parserOptions: { ecmaVersion: 13, sourceType: "module" },
+});
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, messageId) {
+ return { code, errors: [{ messageId: "rejectTopLevelAwait" }] };
+}
+
+ruleTester.run("reject-top-level-await", rule, {
+ valid: [
+ "async() => { await bar() }",
+ "async() => { for await (let x of []) {} }",
+ ],
+ invalid: [
+ invalidCode("await foo"),
+ invalidCode("{ await foo }"),
+ invalidCode("(await foo)"),
+ invalidCode("for await (let x of []) {}"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/rejects-requires-await.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/rejects-requires-await.js
new file mode 100644
index 0000000000..ea6273a8ee
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/rejects-requires-await.js
@@ -0,0 +1,32 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/rejects-requires-await");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, messageId) {
+ return { code, errors: [{ messageId: "rejectRequiresAwait" }] };
+}
+
+ruleTester.run("reject-requires-await", rule, {
+ valid: [
+ "async() => { await Assert.rejects(foo, /assertion/) }",
+ "async() => { await Assert.rejects(foo, /assertion/, 'msg') }",
+ ],
+ invalid: [
+ invalidCode("Assert.rejects(foo)"),
+ invalidCode("Assert.rejects(foo, 'msg')"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-cc-etc.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-cc-etc.js
new file mode 100644
index 0000000000..935d42debb
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-cc-etc.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-cc-etc");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, originalName, newName, output) {
+ return {
+ code,
+ output,
+ errors: [
+ {
+ message: `Use ${newName} rather than ${originalName}`,
+ type: "MemberExpression",
+ },
+ ],
+ };
+}
+
+ruleTester.run("use-cc-etc", rule, {
+ valid: ["Components.Constructor();", "let x = Components.foo;"],
+ invalid: [
+ invalidCode(
+ "let foo = Components.classes['bar'];",
+ "Components.classes",
+ "Cc",
+ "let foo = Cc['bar'];"
+ ),
+ invalidCode(
+ "let bar = Components.interfaces.bar;",
+ "Components.interfaces",
+ "Ci",
+ "let bar = Ci.bar;"
+ ),
+ invalidCode(
+ "Components.results.NS_ERROR_ILLEGAL_INPUT;",
+ "Components.results",
+ "Cr",
+ "Cr.NS_ERROR_ILLEGAL_INPUT;"
+ ),
+ invalidCode(
+ "Components.utils.reportError('fake');",
+ "Components.utils",
+ "Cu",
+ "Cu.reportError('fake');"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-generateqi.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-generateqi.js
new file mode 100644
index 0000000000..26c6b350bc
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-generateqi.js
@@ -0,0 +1,81 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-chromeutils-generateqi");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function callError(message) {
+ return [{ message, type: "CallExpression" }];
+}
+function error(message, type) {
+ return [{ message, type }];
+}
+
+const MSG_NO_JS_QUERY_INTERFACE =
+ "Please use ChromeUtils.generateQI rather than manually creating " +
+ "JavaScript QueryInterface functions";
+
+const MSG_NO_XPCOMUTILS_GENERATEQI =
+ "Please use ChromeUtils.generateQI instead of XPCOMUtils.generateQI";
+
+/* globals nsIFlug */
+function QueryInterface(iid) {
+ if (
+ iid.equals(Ci.nsISupports) ||
+ iid.equals(Ci.nsIMeh) ||
+ iid.equals(nsIFlug) ||
+ iid.equals(Ci.amIFoo)
+ ) {
+ return this;
+ }
+ throw Components.Exception("", Cr.NS_ERROR_NO_INTERFACE);
+}
+
+ruleTester.run("use-chromeutils-generateqi", rule, {
+ valid: [
+ `X.prototype.QueryInterface = ChromeUtils.generateQI(["nsIMeh"]);`,
+ `X.prototype = { QueryInterface: ChromeUtils.generateQI(["nsIMeh"]) }`,
+ ],
+ invalid: [
+ {
+ code: `X.prototype.QueryInterface = XPCOMUtils.generateQI(["nsIMeh"]);`,
+ output: `X.prototype.QueryInterface = ChromeUtils.generateQI(["nsIMeh"]);`,
+ errors: callError(MSG_NO_XPCOMUTILS_GENERATEQI),
+ },
+ {
+ code: `X.prototype = { QueryInterface: XPCOMUtils.generateQI(["nsIMeh"]) };`,
+ output: `X.prototype = { QueryInterface: ChromeUtils.generateQI(["nsIMeh"]) };`,
+ errors: callError(MSG_NO_XPCOMUTILS_GENERATEQI),
+ },
+ {
+ code: `X.prototype = { QueryInterface: ${QueryInterface} };`,
+ output: `X.prototype = { QueryInterface: ChromeUtils.generateQI(["nsIMeh", "nsIFlug", "amIFoo"]) };`,
+ errors: error(MSG_NO_JS_QUERY_INTERFACE, "Property"),
+ },
+ {
+ code: `X.prototype = { ${String(QueryInterface).replace(
+ /^function /,
+ ""
+ )} };`,
+ output: `X.prototype = { QueryInterface: ChromeUtils.generateQI(["nsIMeh", "nsIFlug", "amIFoo"]) };`,
+ errors: error(MSG_NO_JS_QUERY_INTERFACE, "Property"),
+ },
+ {
+ code: `X.prototype.QueryInterface = ${QueryInterface};`,
+ output: `X.prototype.QueryInterface = ChromeUtils.generateQI(["nsIMeh", "nsIFlug", "amIFoo"]);`,
+ errors: error(MSG_NO_JS_QUERY_INTERFACE, "AssignmentExpression"),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-import.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-import.js
new file mode 100644
index 0000000000..92b8aaa360
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-chromeutils-import.js
@@ -0,0 +1,70 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-chromeutils-import");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function callError(message) {
+ return [{ message, type: "CallExpression" }];
+}
+
+const MESSAGE_IMPORT = "Please use ChromeUtils.import instead of Cu.import";
+const MESSAGE_DEFINE =
+ "Please use ChromeUtils.defineModuleGetter instead of " +
+ "XPCOMUtils.defineLazyModuleGetter";
+
+ruleTester.run("use-chromeutils-import", rule, {
+ valid: [
+ `ChromeUtils.import("resource://gre/modules/AppConstants.jsm");`,
+ `ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);`,
+ `ChromeUtils.defineModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");`,
+ `XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm",
+ "Foo");`,
+ `XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm",
+ undefined, preAppConstantsLambda);`,
+ ],
+ invalid: [
+ {
+ code: `Cu.import("resource://gre/modules/AppConstants.jsm");`,
+ output: `ChromeUtils.import("resource://gre/modules/AppConstants.jsm");`,
+ errors: callError(MESSAGE_IMPORT),
+ },
+ {
+ code: `Cu.import("resource://gre/modules/AppConstants.jsm", this);`,
+ output: `ChromeUtils.import("resource://gre/modules/AppConstants.jsm", this);`,
+ errors: callError(MESSAGE_IMPORT),
+ },
+ {
+ code: `Components.utils.import("resource://gre/modules/AppConstants.jsm");`,
+ output: `ChromeUtils.import("resource://gre/modules/AppConstants.jsm");`,
+ errors: callError(MESSAGE_IMPORT),
+ },
+ {
+ code: `Components.utils.import("resource://gre/modules/AppConstants.jsm");`,
+ output: `ChromeUtils.import("resource://gre/modules/AppConstants.jsm");`,
+ errors: callError(MESSAGE_IMPORT),
+ },
+ {
+ code: `XPCOMUtils.defineLazyModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");`,
+ output: `ChromeUtils.defineModuleGetter(this, "AppConstants",
+ "resource://gre/modules/AppConstants.jsm");`,
+ errors: callError(MESSAGE_DEFINE),
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-default-preference-values.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-default-preference-values.js
new file mode 100644
index 0000000000..f4ad001a08
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-default-preference-values.js
@@ -0,0 +1,41 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-default-preference-values");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ let message = "provide a default value instead of using a try/catch block";
+ return { code, errors: [{ message, type: "TryStatement" }] };
+}
+
+let types = ["Bool", "Char", "Float", "Int"];
+let methods = types.map(type => "get" + type + "Pref");
+
+ruleTester.run("use-default-preference-values", rule, {
+ valid: [].concat(
+ methods.map(m => "blah = branch." + m + "('blah', true);"),
+ methods.map(m => "blah = branch." + m + "('blah');"),
+ methods.map(
+ m => "try { canThrow(); blah = branch." + m + "('blah'); } catch(e) {}"
+ )
+ ),
+
+ invalid: [].concat(
+ methods.map(m =>
+ invalidCode("try { blah = branch." + m + "('blah'); } catch(e) {}")
+ )
+ ),
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-includes-instead-of-indexOf.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-includes-instead-of-indexOf.js
new file mode 100644
index 0000000000..cb1810b305
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-includes-instead-of-indexOf.js
@@ -0,0 +1,38 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-includes-instead-of-indexOf");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ let message = "use .includes instead of .indexOf";
+ return { code, errors: [{ message, type: "BinaryExpression" }] };
+}
+
+ruleTester.run("use-includes-instead-of-indexOf", rule, {
+ valid: [
+ "let a = foo.includes(bar);",
+ "let a = foo.indexOf(bar) > 0;",
+ "let a = foo.indexOf(bar) != 0;",
+ ],
+ invalid: [
+ invalidCode("let a = foo.indexOf(bar) >= 0;"),
+ invalidCode("let a = foo.indexOf(bar) != -1;"),
+ invalidCode("let a = foo.indexOf(bar) !== -1;"),
+ invalidCode("let a = foo.indexOf(bar) == -1;"),
+ invalidCode("let a = foo.indexOf(bar) === -1;"),
+ invalidCode("let a = foo.indexOf(bar) < 0;"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-isInstance.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-isInstance.js
new file mode 100644
index 0000000000..7e7b423484
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-isInstance.js
@@ -0,0 +1,130 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-isInstance");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+const errors = [
+ {
+ message:
+ "Please prefer .isInstance() in chrome scripts over the standard instanceof operator for DOM interfaces, " +
+ "since the latter will return false when the object is created from a different context.",
+ type: "BinaryExpression",
+ },
+];
+
+const env = { browser: true };
+
+/**
+ * A test case boilerplate simulating chrome privileged script.
+ * @param {string} code
+ */
+function mockChromeScript(code) {
+ return {
+ code,
+ filename: "foo.sys.mjs",
+ env,
+ };
+}
+
+/**
+ * A test case boilerplate simulating content script.
+ * @param {string} code
+ */
+function mockContentScript(code) {
+ return {
+ code,
+ filename: "foo.js",
+ env,
+ };
+}
+
+ruleTester.run("use-isInstance", rule, {
+ valid: [
+ mockChromeScript("(() => {}) instanceof Function;"),
+ mockChromeScript("({}) instanceof Object;"),
+ mockChromeScript("Node instanceof Object;"),
+ mockChromeScript("node instanceof lazy.Node;"),
+ mockChromeScript("var Node;node instanceof Node;"),
+ mockChromeScript("file instanceof lazy.File;"),
+ mockChromeScript("file instanceof OS.File;"),
+ mockChromeScript("file instanceof OS.File.Error;"),
+ mockChromeScript("file instanceof lazy.OS.File;"),
+ mockChromeScript("file instanceof lazy.OS.File.Error;"),
+ mockChromeScript("file instanceof lazy.lazy.OS.File;"),
+ mockChromeScript("var File;file instanceof File;"),
+ mockChromeScript("foo instanceof RandomGlobalThing;"),
+ mockChromeScript("foo instanceof lazy.RandomGlobalThing;"),
+ mockContentScript("node instanceof Node;"),
+ mockContentScript("file instanceof File;"),
+ mockContentScript(
+ "SpecialPowers.ChromeUtils.import(''); file instanceof File;"
+ ),
+ ],
+ invalid: [
+ {
+ code: "node instanceof Node",
+ output: "Node.isInstance(node)",
+ env,
+ errors,
+ filename: "foo.sys.mjs",
+ },
+ {
+ code: "text instanceof win.Text",
+ output: "win.Text.isInstance(text)",
+ errors,
+ filename: "foo.sys.mjs",
+ },
+ {
+ code: "target instanceof this.contentWindow.HTMLAudioElement",
+ output: "this.contentWindow.HTMLAudioElement.isInstance(target)",
+ errors,
+ filename: "foo.sys.mjs",
+ },
+ {
+ code: "target instanceof File",
+ output: "File.isInstance(target)",
+ env,
+ errors,
+ filename: "foo.sys.mjs",
+ },
+ {
+ code: "target instanceof win.File",
+ output: "win.File.isInstance(target)",
+ errors,
+ filename: "foo.sys.mjs",
+ },
+ {
+ code: "window.arguments[0] instanceof window.XULElement",
+ output: "window.XULElement.isInstance(window.arguments[0])",
+ errors,
+ filename: "foo.sys.mjs",
+ },
+ {
+ code: "ChromeUtils.import(''); node instanceof Node",
+ output: "ChromeUtils.import(''); Node.isInstance(node)",
+ env,
+ errors,
+ filename: "foo.js",
+ },
+ {
+ code: "ChromeUtils.import(''); file instanceof File",
+ output: "ChromeUtils.import(''); File.isInstance(file)",
+ env,
+ errors,
+ filename: "foo.js",
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-ownerGlobal.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-ownerGlobal.js
new file mode 100644
index 0000000000..b08bdf1632
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-ownerGlobal.js
@@ -0,0 +1,35 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-ownerGlobal");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code) {
+ let message = "use .ownerGlobal instead of .ownerDocument.defaultView";
+ return { code, errors: [{ message, type: "MemberExpression" }] };
+}
+
+ruleTester.run("use-ownerGlobal", rule, {
+ valid: [
+ "aEvent.target.ownerGlobal;",
+ "this.DOMPointNode.ownerGlobal.getSelection();",
+ "windowToMessageManager(node.ownerGlobal);",
+ ],
+ invalid: [
+ invalidCode("aEvent.target.ownerDocument.defaultView;"),
+ invalidCode("this.DOMPointNode.ownerDocument.defaultView.getSelection();"),
+ invalidCode("windowToMessageManager(node.ownerDocument.defaultView);"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-returnValue.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-returnValue.js
new file mode 100644
index 0000000000..81952452e4
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-returnValue.js
@@ -0,0 +1,39 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-returnValue");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, methodName) {
+ let message = `{Array/String}.${methodName} doesn't modify the instance in-place`;
+ return { code, errors: [{ message, type: "ExpressionStatement" }] };
+}
+
+ruleTester.run("use-returnValue", rule, {
+ valid: [
+ "a = foo.concat(bar)",
+ "b = bar.concat([1,3,4])",
+ "c = baz.concat()",
+ "d = qux.join(' ')",
+ "e = quux.slice(1)",
+ ],
+ invalid: [
+ invalidCode("foo.concat(bar)", "concat"),
+ invalidCode("bar.concat([1,3,4])", "concat"),
+ invalidCode("baz.concat()", "concat"),
+ invalidCode("qux.join(' ')", "join"),
+ invalidCode("quux.slice(1)", "slice"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-services.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-services.js
new file mode 100644
index 0000000000..4c8a6359dd
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-services.js
@@ -0,0 +1,60 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-services");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, name, replaces, type = "CallExpression") {
+ let message = `Use Services.${name} rather than ${replaces}.`;
+ return { code, errors: [{ message, type }] };
+}
+
+ruleTester.run("use-services", rule, {
+ valid: [
+ 'Cc["@mozilla.org/fakeservice;1"].getService(Ci.nsIFake)',
+ 'Components.classes["@mozilla.org/fakeservice;1"].getService(Components.interfaces.nsIFake)',
+ "Services.wm.addListener()",
+ ],
+ invalid: [
+ invalidCode(
+ 'Cc["@mozilla.org/appshell/window-mediator;1"].getService(Ci.nsIWindowMediator);',
+ "wm",
+ "getService()"
+ ),
+ invalidCode(
+ 'Components.classes["@mozilla.org/toolkit/app-startup;1"].getService(Components.interfaces.nsIAppStartup);',
+ "startup",
+ "getService()"
+ ),
+ invalidCode(
+ `XPCOMUtils.defineLazyServiceGetters(this, {
+ uuidGen: ["@mozilla.org/uuid-generator;1", "nsIUUIDGenerator"],
+ });`,
+ "uuid",
+ "defineLazyServiceGetters",
+ "ArrayExpression"
+ ),
+ invalidCode(
+ `XPCOMUtils.defineLazyServiceGetter(
+ this,
+ "gELS",
+ "@mozilla.org/eventlistenerservice;1",
+ "nsIEventListenerService"
+ );`,
+ "els",
+ "defineLazyServiceGetter"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/use-static-import.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-static-import.js
new file mode 100644
index 0000000000..e4d28861ad
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/use-static-import.js
@@ -0,0 +1,88 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/use-static-import");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({
+ parserOptions: { ecmaVersion: 13, sourceType: "module" },
+});
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function callError() {
+ return [{ messageId: "useStaticImport", type: "VariableDeclaration" }];
+}
+
+ruleTester.run("use-static-import", rule, {
+ valid: [
+ {
+ // Already converted, no issues.
+ code:
+ 'import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs";',
+ filename: "test.sys.mjs",
+ },
+ {
+ // Inside an if statement.
+ code:
+ 'if (foo) { const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs") }',
+ filename: "test.sys.mjs",
+ },
+ {
+ // Inside a function.
+ code:
+ 'function foo() { const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs") }',
+ filename: "test.sys.mjs",
+ },
+ {
+ // importESModule with two args cannot be converted.
+ code:
+ 'const { f } = ChromeUtils.importESModule("some/module.sys.mjs", { loadInDevToolsLoader : true });',
+ filename: "test.sys.mjs",
+ },
+ {
+ // A non-system file attempting to import a system file should not be
+ // converted.
+ code:
+ 'const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs")',
+ filename: "test.mjs",
+ },
+ ],
+ invalid: [
+ {
+ // Simple import in system module should be converted.
+ code:
+ 'const { XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs")',
+ errors: callError(),
+ filename: "test.sys.mjs",
+ output:
+ 'import { XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"',
+ },
+ {
+ // Should handle rewritten variables as well.
+ code:
+ 'const { XPCOMUtils: foo } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs")',
+ errors: callError(),
+ filename: "test.sys.mjs",
+ output:
+ 'import { XPCOMUtils as foo } from "resource://gre/modules/XPCOMUtils.sys.mjs"',
+ },
+ {
+ // Should handle multiple variables.
+ code:
+ 'const { foo, XPCOMUtils } = ChromeUtils.importESModule("resource://gre/modules/XPCOMUtils.sys.mjs")',
+ errors: callError(),
+ filename: "test.sys.mjs",
+ output:
+ 'import { foo, XPCOMUtils } from "resource://gre/modules/XPCOMUtils.sys.mjs"',
+ },
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-ci-uses.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-ci-uses.js
new file mode 100644
index 0000000000..e351711944
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-ci-uses.js
@@ -0,0 +1,58 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var os = require("os");
+var rule = require("../lib/rules/valid-ci-uses");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 13 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, messageId, data) {
+ return { code, errors: [{ messageId, data }] };
+}
+
+process.env.MOZ_XPT_ARTIFACTS_DIR = `${__dirname}/xpidl`;
+
+const tests = {
+ valid: ["Ci.nsIURIFixup", "Ci.nsIURIFixup.FIXUP_FLAG_NONE"],
+ invalid: [
+ invalidCode("Ci.nsIURIFixup.UNKNOWN_CONSTANT", "unknownProperty", {
+ interface: "nsIURIFixup",
+ property: "UNKNOWN_CONSTANT",
+ }),
+ invalidCode("Ci.nsIFoo", "unknownInterface", {
+ interface: "nsIFoo",
+ }),
+ ],
+};
+
+// For ESLint tests, we only have a couple of xpt examples in the xpidl directory.
+// Therefore we can pretend that these interfaces no longer exist.
+switch (os.platform) {
+ case "windows":
+ tests.invalid.push(
+ invalidCode("Ci.nsIJumpListShortcut", "missingInterface")
+ );
+ break;
+ case "darwin":
+ tests.invalid.push(
+ invalidCode("Ci.nsIMacShellService", "missingInterface")
+ );
+ break;
+ case "linux":
+ tests.invalid.push(
+ invalidCode("Ci.mozISandboxReporter", "missingInterface")
+ );
+}
+
+ruleTester.run("valid-ci-uses", rule, tests);
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js
new file mode 100644
index 0000000000..6e207584fa
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-lazy.js
@@ -0,0 +1,151 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/valid-lazy");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 13 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, name, messageId) {
+ return { code, errors: [{ messageId, data: { name } }] };
+}
+
+ruleTester.run("valid-lazy", rule, {
+ // Note: these tests build on top of one another, although lazy gets
+ // re-declared, it
+ valid: [
+ `
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", () => {});
+ if (x) { lazy.foo.bar(); }
+ `,
+ `
+ const lazy = {};
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ foo: "foo.jsm",
+ });
+ if (x) { lazy.foo.bar(); }
+ `,
+ `
+ const lazy = {};
+ ChromeUtils.defineESModuleGetters(lazy, {
+ foo: "foo.mjs",
+ });
+ if (x) { lazy.foo.bar(); }
+ `,
+ `
+ const lazy = {};
+ Integration.downloads.defineESModuleGetter(lazy, "foo", "foo.sys.mjs");
+ if (x) { lazy.foo.bar(); }
+ `,
+ `
+ const lazy = createLazyLoaders({ foo: () => {}});
+ if (x) { lazy.foo.bar(); }
+ `,
+ `
+ const lazy = {};
+ loader.lazyRequireGetter(
+ lazy,
+ ["foo1", "foo2"],
+ "bar",
+ true
+ );
+ if (x) {
+ lazy.foo1.bar();
+ lazy.foo2.bar();
+ }
+ `,
+ // Test for top-level unconditional.
+ `
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", () => {});
+ if (x) { lazy.foo.bar(); }
+ for (;;) { lazy.foo.bar(); }
+ for (var x in y) { lazy.foo.bar(); }
+ for (var x of y) { lazy.foo.bar(); }
+ while (true) { lazy.foo.bar(); }
+ do { lazy.foo.bar(); } while (true);
+ switch (x) { case 1: lazy.foo.bar(); }
+ try { lazy.foo.bar(); } catch (e) {}
+ function f() { lazy.foo.bar(); }
+ (function f() { lazy.foo.bar(); });
+ () => { lazy.foo.bar(); };
+ class C {
+ constructor() { lazy.foo.bar(); }
+ foo() { lazy.foo.bar(); }
+ get x() { lazy.foo.bar(); }
+ set x(v) { lazy.foo.bar(); }
+ a = lazy.foo.bar();
+ #b = lazy.foo.bar();
+ static {
+ lazy.foo.bar();
+ }
+ }
+ a && lazy.foo.bar();
+ a || lazy.foo.bar();
+ a ?? lazy.foo.bar();
+ a ? lazy.foo.bar() : b;
+ a?.b[lazy.foo.bar()];
+ a ||= lazy.foo.bar();
+ a &&= lazy.foo.bar();
+ a ??= lazy.foo.bar();
+ var { x = lazy.foo.bar() } = {};
+ var [ y = lazy.foo.bar() ] = [];
+ `,
+ ],
+ invalid: [
+ invalidCode("if (x) { lazy.bar; }", "bar", "unknownProperty"),
+ invalidCode(
+ `
+ const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");
+ XPCOMUtils.defineLazyGetter(lazy, "foo", "foo1.jsm");
+ if (x) { lazy.foo.bar(); }
+ `,
+ "foo",
+ "duplicateSymbol"
+ ),
+ invalidCode(
+ `
+ const lazy = {};
+ XPCOMUtils.defineLazyModuleGetters(lazy, {
+ "foo-bar": "foo.jsm",
+ });
+ if (x) { lazy["foo-bar"].bar(); }
+ `,
+ "foo-bar",
+ "incorrectType"
+ ),
+ invalidCode(
+ `const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo", "foo.jsm");
+ `,
+ "foo",
+ "unusedProperty"
+ ),
+ invalidCode(
+ `const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo1", () => {});
+ lazy.foo1.bar();`,
+ "foo1",
+ "topLevelAndUnconditional"
+ ),
+ invalidCode(
+ `const lazy = {};
+ XPCOMUtils.defineLazyGetter(lazy, "foo1", () => {});
+ { x = -f(1 + lazy.foo1.bar()); }`,
+ "foo1",
+ "topLevelAndUnconditional"
+ ),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services-property.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services-property.js
new file mode 100644
index 0000000000..005393f431
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services-property.js
@@ -0,0 +1,42 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/valid-services-property");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 13 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, messageId, data) {
+ return { code, errors: [{ messageId, data }] };
+}
+
+process.env.MOZ_XPT_ARTIFACTS_DIR = `${__dirname}/xpidl`;
+
+ruleTester.run("valid-services-property", rule, {
+ valid: [
+ "Services.uriFixup.keywordToURI()",
+ "Services.uriFixup.FIXUP_FLAG_NONE",
+ ],
+ invalid: [
+ invalidCode("Services.uriFixup.UNKNOWN_CONSTANT", "unknownProperty", {
+ alias: "uriFixup",
+ propertyName: "UNKNOWN_CONSTANT",
+ checkedInterfaces: ["nsIURIFixup"],
+ }),
+ invalidCode("Services.uriFixup.foo()", "unknownProperty", {
+ alias: "uriFixup",
+ propertyName: "foo",
+ checkedInterfaces: ["nsIURIFixup"],
+ }),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services.js b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services.js
new file mode 100644
index 0000000000..7a2c192b93
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/valid-services.js
@@ -0,0 +1,33 @@
+/* Any copyright is dedicated to the Public Domain.
+ * http://creativecommons.org/publicdomain/zero/1.0/ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Requirements
+// ------------------------------------------------------------------------------
+
+var rule = require("../lib/rules/valid-services");
+var RuleTester = require("eslint").RuleTester;
+
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6 } });
+
+// ------------------------------------------------------------------------------
+// Tests
+// ------------------------------------------------------------------------------
+
+function invalidCode(code, name) {
+ let message = `Unknown Services member property ${name}`;
+ return { code, errors: [{ message }] };
+}
+
+ruleTester.run("valid-services", rule, {
+ valid: ["Services.crashmanager", "lazy.Services.crashmanager"],
+ invalid: [
+ invalidCode("Services.foo", "foo"),
+ invalidCode("Services.foo()", "foo"),
+ invalidCode("lazy.Services.foo", "foo"),
+ invalidCode("Services.foo.bar()", "foo"),
+ invalidCode("lazy.Services.foo.bar()", "foo"),
+ ],
+});
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/docshell.xpt b/tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/docshell.xpt
new file mode 100644
index 0000000000..abb593296b
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/docshell.xpt
@@ -0,0 +1,6077 @@
+[
+ {
+ "consts": [
+ {
+ "name": "ePrompt",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 0
+ },
+ {
+ "name": "eDontPromptAndDontUnload",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ },
+ {
+ "name": "eDontPromptAndUnload",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 2
+ },
+ {
+ "name": "eAllowNavigation",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 0
+ },
+ {
+ "name": "eRequestBlockNavigation",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ },
+ {
+ "name": "eDelayResize",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 1
+ }
+ ],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "init",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "container",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShell",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "container",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShell",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "loadStart",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/Document.h",
+ "name": "Document",
+ "native": "mozilla::dom::Document",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "loadComplete",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "loadCompleted",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "isStopped",
+ "params": []
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "permitUnload",
+ "params": [
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "inPermitUnload",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "dispatchBeforeUnload",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "beforeUnloadFiring",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "pageHide",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "close",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISHEntry",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "destroy",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "stop",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "DOMDocument",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/Document.h",
+ "name": "Document",
+ "native": "mozilla::dom::Document",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getDocument",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setDocument",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/Document.h",
+ "name": "Document",
+ "native": "mozilla::dom::Document",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getBounds",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setBounds",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setBoundsWithFlags",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "previousViewer",
+ "params": []
+ },
+ {
+ "flags": [
+ "setter",
+ "hidden"
+ ],
+ "name": "previousViewer",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIContentViewer",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "move",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "show",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "hide",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "sticky",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "sticky",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "open",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISHEntry",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "clearHistoryEntry",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "setPageModeForTesting",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrintSettings",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "setPrintSettingsForSubdocument",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrintSettings",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "historyEntry",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsISHEntry",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isTabModalPromptAllowed",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isHidden",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "isHidden",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "presShell",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "presContext",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setDocumentInternal",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/Document.h",
+ "name": "Document",
+ "native": "mozilla::dom::Document",
+ "tag": "TD_DOMOBJECT"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "findContainerView",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setNavigationTiming",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "deviceFullZoomForTest",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_FLOAT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "authorStyleDisabled",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "authorStyleDisabled",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "getContentSize",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "getContentSizeConstrained",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getReloadEncodingAndSource",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setReloadEncodingAndSource",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "forgetReloadEncoding",
+ "params": []
+ }
+ ],
+ "name": "nsIContentViewer",
+ "parent": "nsISupports",
+ "uuid": "2da17016-7851-4a45-a7a8-00b360e01595"
+ },
+ {
+ "consts": [
+ {
+ "name": "COPY_IMAGE_TEXT",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "COPY_IMAGE_HTML",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "COPY_IMAGE_DATA",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 4
+ },
+ {
+ "name": "COPY_IMAGE_ALL",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": -1
+ }
+ ],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "clearSelection",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "selectAll",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "copySelection",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "copyable",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "copyLinkLocation",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "inLink",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "copyImage",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "inImage",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getContents",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "canGetContents",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "setCommandNode",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "nsIContent.h",
+ "name": "Node",
+ "native": "nsINode",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIContentViewerEdit",
+ "parent": "nsISupports",
+ "uuid": "35be2d7e-f29b-48ec-bf7e-80a30a724de3"
+ },
+ {
+ "consts": [
+ {
+ "name": "ENUMERATE_FORWARDS",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 0
+ },
+ {
+ "name": "ENUMERATE_BACKWARDS",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ },
+ {
+ "name": "APP_TYPE_UNKNOWN",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 0
+ },
+ {
+ "name": "APP_TYPE_MAIL",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ },
+ {
+ "name": "APP_TYPE_EDITOR",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 2
+ },
+ {
+ "name": "BUSY_FLAGS_NONE",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 0
+ },
+ {
+ "name": "BUSY_FLAGS_BUSY",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ },
+ {
+ "name": "BUSY_FLAGS_BEFORE_PAGE_LOAD",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 2
+ },
+ {
+ "name": "BUSY_FLAGS_PAGE_LOADING",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 4
+ },
+ {
+ "name": "LOAD_CMD_NORMAL",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ },
+ {
+ "name": "LOAD_CMD_RELOAD",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 2
+ },
+ {
+ "name": "LOAD_CMD_HISTORY",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 4
+ },
+ {
+ "name": "LOAD_CMD_PUSHSTATE",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 8
+ },
+ {
+ "name": "META_VIEWPORT_OVERRIDE_DISABLED",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 0
+ },
+ {
+ "name": "META_VIEWPORT_OVERRIDE_ENABLED",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ },
+ {
+ "name": "META_VIEWPORT_OVERRIDE_NONE",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 2
+ }
+ ],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "setCancelContentJSEpoch",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "loadURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "jscontext"
+ ],
+ "name": "addState",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "prepareForNewContentModel",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "setCurrentURIForSessionStore",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "firePageHideNotification",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "presContext",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "presShell",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "eldestPresShell",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "contentViewer",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIContentViewer",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "outerWindowID",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "chromeEventHandler",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/EventTarget.h",
+ "name": "EventTarget",
+ "native": "mozilla::dom::EventTarget",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "chromeEventHandler",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/EventTarget.h",
+ "name": "EventTarget",
+ "native": "mozilla::dom::EventTarget",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "customUserAgent",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "customUserAgent",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "cssErrorReportingEnabled",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "cssErrorReportingEnabled",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowPlugins",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowPlugins",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowMetaRedirects",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowMetaRedirects",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowSubframes",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowSubframes",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowImages",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowImages",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowMedia",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowMedia",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowDNSPrefetch",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowDNSPrefetch",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowWindowControl",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowWindowControl",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowContentRetargeting",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowContentRetargeting",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowContentRetargetingOnChildren",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowContentRetargetingOnChildren",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getAllDocShellsInSubtree",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "element": {
+ "name": "nsIDocShell",
+ "tag": "TD_INTERFACE_TYPE"
+ },
+ "tag": "TD_ARRAY"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "appType",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "appType",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "allowAuth",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "allowAuth",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "zoom",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_FLOAT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "zoom",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_FLOAT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "tabToTreeOwner",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "busyFlags",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "loadType",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "loadType",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "defaultLoadFlags",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "defaultLoadFlags",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "isBeingDestroyed",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isExecutingOnLoadHandler",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "layoutHistoryState",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsILayoutHistoryState",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "layoutHistoryState",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsILayoutHistoryState",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "loadURIDelegate",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsILoadURIDelegate",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "suspendRefreshURIs",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "resumeRefreshURIs",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "beginRestore",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIContentViewer",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "finishRestore",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "clearCachedUserAgent",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "clearCachedPlatform",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "restoringDocument",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "useErrorPages",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "useErrorPages",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "displayLoadError",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PWSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "name": "nsIChannel",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "failedChannel",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIChannel",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "previousEntryIndex",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "loadedEntryIndex",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "historyPurged",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "currentDocumentChannel",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIChannel",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isInUnload",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "DetachEditorFromWindow",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "exitPrintPreview",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "historyID",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_NSID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "HistoryID",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isAppTab",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "isAppTab",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "createAboutBlankContentViewer",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrincipal",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrincipal",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "name": "nsIContentSecurityPolicy",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "charset",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "forceEncodingDetection",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setParentCharset",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrincipal",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getParentCharset",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIPrincipal",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "recordProfileTimelineMarkers",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "recordProfileTimelineMarkers",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "now",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "popProfileTimelineMarkers",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "addWeakPrivacyTransitionObserver",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrivacyTransitionObserver",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "addWeakReflowObserver",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIReflowObserver",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "removeWeakReflowObserver",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIReflowObserver",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "notifyReflowObservers",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "addWeakScrollObserver",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIScrollObserver",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "removeWeakScrollObserver",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIScrollObserver",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "notifyScrollObservers",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isTopLevelContentDocShell",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getSameTypeInProcessParentIgnoreBrowserBoundaries",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShell",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "asyncPanZoomEnabled",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "pluginsAllowedInCurrentDoc",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "mayEnableCharacterEncodingMenu",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "editor",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIEditor",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "editor",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIEditor",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "editable",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "hasEditingSession",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "makeEditable",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getCurrentSHEntry",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsISHEntry",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "isCommandEnabled",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "doCommand",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "doCommandWithParams",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsICommandParams",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "IsInvisible",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "SetInvisible",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "GetScriptGlobalObject",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getExtantDocument",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "deviceSizeIsPageSize",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "deviceSizeIsPageSize",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "notifyJSRunToCompletionStart",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "notifyJSRunToCompletionStop",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "hasLoadedNonBlankURI",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "windowDraggingAllowed",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "windowDraggingAllowed",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "currentScrollRestorationIsManual",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "currentScrollRestorationIsManual",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "getOriginAttributes",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "jscontext"
+ ],
+ "name": "setOriginAttributes",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "editingSession",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIEditingSession",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "browserChild",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIBrowserChild",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "GetBrowserChild",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "GetCommandManager",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "metaViewportOverride",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "metaViewportOverride",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "useTrackingProtection",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "useTrackingProtection",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "dispatchLocationChangeEvent",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "startDelayedAutoplayMediaComponents",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "TakeInitialClientSource",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "setColorMatrix",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "element": {
+ "tag": "TD_FLOAT"
+ },
+ "tag": "TD_ARRAY"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isForceReloading",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getColorMatrix",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "element": {
+ "tag": "TD_FLOAT"
+ },
+ "tag": "TD_ARRAY"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "messageManager",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/ContentFrameMessageManager.h",
+ "name": "ContentFrameMessageManager",
+ "native": "mozilla::dom::ContentFrameMessageManager",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getHasTrackingContentBlocked",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_PROMISE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hidden",
+ "hasretval"
+ ],
+ "name": "isAttemptingToNavigate",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isNavigating",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "synchronizeLayoutHistoryState",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "persistLayoutHistoryState",
+ "params": []
+ }
+ ],
+ "name": "nsIDocShell",
+ "parent": "nsIDocShellTreeItem",
+ "uuid": "049234fe-da10-478b-bc5d-bc6f9a1ba63d"
+ },
+ {
+ "consts": [
+ {
+ "name": "typeChrome",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 0
+ },
+ {
+ "name": "typeContent",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "typeContentWrapper",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "typeChromeWrapper",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 3
+ },
+ {
+ "name": "typeAll",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 2147483647
+ }
+ ],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "name",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "name",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "nameEquals",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "itemType",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "ItemType",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "parent",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "sameTypeParent",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "rootTreeItem",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "sameTypeRootTreeItem",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "treeOwner",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeOwner",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "setTreeOwner",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeOwner",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "childCount",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "addChild",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "removeChild",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getChildAt",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "browsingContext",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/BrowsingContext.h",
+ "name": "BrowsingContext",
+ "native": "mozilla::dom::BrowsingContext",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getBrowsingContext",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "domWindow",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "mozIDOMWindowProxy",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getDocument",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getWindow",
+ "params": []
+ }
+ ],
+ "name": "nsIDocShellTreeItem",
+ "parent": "nsISupports",
+ "uuid": "9b7c586f-9214-480c-a2c4-49b526fff1a6"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "contentShellAdded",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "contentShellRemoved",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "primaryContentShell",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "remoteTabAdded",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIRemoteTab",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "remoteTabRemoved",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIRemoteTab",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "primaryRemoteTab",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIRemoteTab",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "primaryContentBrowsingContext",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/BrowsingContext.h",
+ "name": "BrowsingContext",
+ "native": "mozilla::dom::BrowsingContext",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "sizeShellTo",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShellTreeItem",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "getPrimaryContentSize",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "setPrimaryContentSize",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "getRootShellSize",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "setRootShellSize",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "setPersistence",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "getPersistence",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "tabCount",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "hasPrimaryContent",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIDocShellTreeOwner",
+ "parent": "nsISupports",
+ "uuid": "0e3dc4b1-4cea-4a37-af71-79f0afd07574"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "createInstance",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIChannel",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsILoadGroup",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShell",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIStreamListener",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIContentViewer",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "createInstanceForDocument",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/Document.h",
+ "name": "Document",
+ "native": "mozilla::dom::Document",
+ "tag": "TD_DOMOBJECT"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIContentViewer",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIDocumentLoaderFactory",
+ "parent": "nsISupports",
+ "uuid": "e795239e-9d3c-47c4-b063-9e600fb3b287"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "associatedWindow",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "mozIDOMWindowProxy",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "topWindow",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "mozIDOMWindowProxy",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "topFrameElement",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/Element.h",
+ "name": "Element",
+ "native": "mozilla::dom::Element",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isContent",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "usePrivateBrowsing",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "usePrivateBrowsing",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "useRemoteTabs",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "useRemoteSubframes",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "useTrackingProtection",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "useTrackingProtection",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "SetPrivateBrowsing",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "SetRemoteTabs",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "SetRemoteSubframes",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "originAttributes",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "GetOriginAttributes",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsILoadContext",
+ "parent": "nsISupports",
+ "uuid": "2813a7a3-d084-4d00-acd0-f76620315c02"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "loadURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT16"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrincipal",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "handleLoadError",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT16"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsILoadURIDelegate",
+ "parent": "nsISupports",
+ "uuid": "78e42d37-a34c-4d96-b901-25385669aba4"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "function"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "privateModeChanged",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIPrivacyTransitionObserver",
+ "parent": "nsISupports",
+ "uuid": "b4b1449d-0ef0-47f5-b62e-adc57fd49702"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "reflow",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "reflowInterruptible",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIReflowObserver",
+ "parent": "nsISupports",
+ "uuid": "832e692c-c4a6-11e2-8fd1-dce678957a39"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "refreshURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrincipal",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "forceRefreshURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIPrincipal",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "cancelRefreshURITimers",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "refreshPending",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIRefreshURI",
+ "parent": "nsISupports",
+ "uuid": "a5e61a3c-51bd-45be-ac0c-e87b71860656"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "onShowTooltip",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "onHideTooltip",
+ "params": []
+ }
+ ],
+ "name": "nsITooltipListener",
+ "parent": "nsISupports",
+ "uuid": "44b78386-1dd2-11b2-9ad2-e4eee2ca1916"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getNodeText",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "nsIContent.h",
+ "name": "Node",
+ "native": "nsINode",
+ "tag": "TD_DOMOBJECT"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_PWSTRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_PWSTRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsITooltipTextProvider",
+ "parent": "nsISupports",
+ "uuid": "b128a1e6-44f3-4331-8fbe-5af360ff21ee"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "consumer",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/BrowsingContext.h",
+ "name": "BrowsingContext",
+ "native": "mozilla::dom::BrowsingContext",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "consumer",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/BrowsingContext.h",
+ "name": "BrowsingContext",
+ "native": "mozilla::dom::BrowsingContext",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "preferredURI",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "preferredURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "fixedURI",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "fixedURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "keywordProviderName",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "keywordProviderName",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "keywordAsSent",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "keywordAsSent",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "fixupChangedProtocol",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "fixupChangedProtocol",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "fixupCreatedAlternateURI",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "fixupCreatedAlternateURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "originalInput",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "originalInput",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "postData",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIInputStream",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "postData",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIInputStream",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIURIFixupInfo",
+ "parent": "nsISupports",
+ "uuid": "4819f183-b532-4932-ac09-b309cd853be7"
+ },
+ {
+ "consts": [
+ {
+ "name": "FIXUP_FLAG_NONE",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 0
+ },
+ {
+ "name": "FIXUP_FLAG_ALLOW_KEYWORD_LOOKUP",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "FIXUP_FLAGS_MAKE_ALTERNATE_URI",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "FIXUP_FLAG_PRIVATE_CONTEXT",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 4
+ },
+ {
+ "name": "FIXUP_FLAG_FIX_SCHEME_TYPOS",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 8
+ },
+ {
+ "name": "FIXUP_FLAG_FORCE_ALTERNATE_URI",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 16
+ }
+ ],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getFixupURIInfo",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIURIFixupInfo",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "webNavigationFlagsToFixupFlags",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "keywordToURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIURIFixupInfo",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "forceHttpFixup",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIURIFixupInfo",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "checkHost",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDNSListener",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "isDomainKnown",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIURIFixup",
+ "parent": "nsISupports",
+ "uuid": "1da7e9d4-620b-4949-849a-1cd6077b1b2d"
+ },
+ {
+ "consts": [
+ {
+ "name": "LOAD_FLAGS_MASK",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 65535
+ },
+ {
+ "name": "LOAD_FLAGS_NONE",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 0
+ },
+ {
+ "name": "LOAD_FLAGS_IS_REFRESH",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 16
+ },
+ {
+ "name": "LOAD_FLAGS_IS_LINK",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 32
+ },
+ {
+ "name": "LOAD_FLAGS_BYPASS_HISTORY",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 64
+ },
+ {
+ "name": "LOAD_FLAGS_REPLACE_HISTORY",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 128
+ },
+ {
+ "name": "LOAD_FLAGS_BYPASS_CACHE",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 256
+ },
+ {
+ "name": "LOAD_FLAGS_BYPASS_PROXY",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 512
+ },
+ {
+ "name": "LOAD_FLAGS_CHARSET_CHANGE",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 1024
+ },
+ {
+ "name": "LOAD_FLAGS_STOP_CONTENT",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 2048
+ },
+ {
+ "name": "LOAD_FLAGS_FROM_EXTERNAL",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 4096
+ },
+ {
+ "name": "LOAD_FLAGS_FIRST_LOAD",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 16384
+ },
+ {
+ "name": "LOAD_FLAGS_ALLOW_POPUPS",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 32768
+ },
+ {
+ "name": "LOAD_FLAGS_BYPASS_CLASSIFIER",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 65536
+ },
+ {
+ "name": "LOAD_FLAGS_FORCE_ALLOW_COOKIES",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 131072
+ },
+ {
+ "name": "LOAD_FLAGS_DISALLOW_INHERIT_PRINCIPAL",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 262144
+ },
+ {
+ "name": "LOAD_FLAGS_ERROR_LOAD_CHANGES_RV",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 524288
+ },
+ {
+ "name": "LOAD_FLAGS_ALLOW_THIRD_PARTY_FIXUP",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 1048576
+ },
+ {
+ "name": "LOAD_FLAGS_FIXUP_SCHEME_TYPOS",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 2097152
+ },
+ {
+ "name": "LOAD_FLAGS_FORCE_ALLOW_DATA_URI",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 4194304
+ },
+ {
+ "name": "LOAD_FLAGS_IS_REDIRECT",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 8388608
+ },
+ {
+ "name": "LOAD_FLAGS_DISABLE_TRR",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 16777216
+ },
+ {
+ "name": "LOAD_FLAGS_FORCE_TRR",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 33554432
+ },
+ {
+ "name": "LOAD_FLAGS_BYPASS_LOAD_URI_DELEGATE",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 67108864
+ },
+ {
+ "name": "LOAD_FLAGS_USER_ACTIVATION",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 134217728
+ },
+ {
+ "name": "STOP_NETWORK",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "STOP_CONTENT",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "STOP_ALL",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 3
+ }
+ ],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "canGoBack",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "canGoForward",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "goBack",
+ "params": [
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "goForward",
+ "params": [
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "gotoIndex",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in",
+ "optional"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "jscontext"
+ ],
+ "name": "loadURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "binaryLoadURI",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "reload",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "stop",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "document",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "headerFile": "mozilla/dom/Document.h",
+ "name": "Document",
+ "native": "mozilla::dom::Document",
+ "tag": "TD_DOMOBJECT"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "currentURI",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIURI",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "sessionHistory",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "resumeRedirectedLoad",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT64"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIWebNavigation",
+ "parent": "nsISupports",
+ "uuid": "3ade79d4-8cb9-4952-b18d-4f9b63ca0d31"
+ },
+ {
+ "consts": [
+ {
+ "name": "UNSUPPORTED",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 0
+ },
+ {
+ "name": "IMAGE",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "FALLBACK",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "OTHER",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 32768
+ }
+ ],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "isTypeSupported",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIWebNavigationInfo",
+ "parent": "nsISupports",
+ "uuid": "62a93afb-93a1-465c-84c8-0432264229de"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "loadPageAsViewSource",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDocShell",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "currentDescriptor",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIWebPageDescriptor",
+ "parent": "nsISupports",
+ "uuid": "6f30b676-3710-4c2c-80b1-0395fb26516e"
+ }
+] \ No newline at end of file
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/xpcom_base.xpt b/tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/xpcom_base.xpt
new file mode 100644
index 0000000000..943b2c8e87
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/tests/xpidl/xpcom_base.xpt
@@ -0,0 +1,3197 @@
+[
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "unloadTabAsync",
+ "params": []
+ }
+ ],
+ "name": "nsITabUnloader",
+ "parent": "nsISupports",
+ "uuid": "2e530956-6054-464f-9f4c-0ae6f8de5523"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "registerTabUnloader",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsITabUnloader",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "onUnloadAttemptCompleted",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIAvailableMemoryWatcherBase",
+ "parent": "nsISupports",
+ "uuid": "b0b5701e-239d-49db-9009-37e89f86441c"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "function"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "observe",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIConsoleMessage",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIConsoleListener",
+ "parent": "nsISupports",
+ "uuid": "35c400a4-5792-438c-b915-65e30d58d557"
+ },
+ {
+ "consts": [
+ {
+ "name": "debug",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 0
+ },
+ {
+ "name": "info",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "warn",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "error",
+ "type": {
+ "tag": "TD_UINT32"
+ },
+ "value": 3
+ }
+ ],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "logLevel",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "timeStamp",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "microSecondTimeStamp",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "message",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isForwardedFromContentProcess",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "isForwardedFromContentProcess",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "toString",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIConsoleMessage",
+ "parent": "nsISupports",
+ "uuid": "3aba9617-10e2-4839-83ae-2e6fc4df428b"
+ },
+ {
+ "consts": [
+ {
+ "name": "SuppressLog",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 0
+ },
+ {
+ "name": "OutputToLog",
+ "type": {
+ "tag": "TD_UINT8"
+ },
+ "value": 1
+ }
+ ],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "logMessage",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIConsoleMessage",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "logMessageWithMode",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIConsoleMessage",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT8"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "logStringMessage",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PWSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getMessageArray",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "element": {
+ "name": "nsIConsoleMessage",
+ "tag": "TD_INTERFACE_TYPE"
+ },
+ "tag": "TD_ARRAY"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "registerListener",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIConsoleListener",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "unregisterListener",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIConsoleListener",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "reset",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "resetWindow",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT64"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIConsoleService",
+ "parent": "nsISupports",
+ "uuid": "0eb81d20-c37e-42d4-82a8-ca9ae96bdf52"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "noteRefCountedObject",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "noteGCedObject",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "noteEdge",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "describeRoot",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "describeGarbage",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsICycleCollectorHandler",
+ "parent": "nsISupports",
+ "uuid": "7f093367-1492-4b89-87af-c01dbc831246"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "open",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "closeGCLog",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "closeCCLog",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "filenameIdentifier",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "filenameIdentifier",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "processIdentifier",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "processIdentifier",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "gcLog",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIFile",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "ccLog",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIFile",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsICycleCollectorLogSink",
+ "parent": "nsISupports",
+ "uuid": "3ad9875f-d0e4-4ac2-87e3-f127f6c02ce1"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "allTraces",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsICycleCollectorListener",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "wantAllTraces",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "disableLog",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "disableLog",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "logSink",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsICycleCollectorLogSink",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "logSink",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsICycleCollectorLogSink",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "wantAfterProcessing",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "setter"
+ ],
+ "name": "wantAfterProcessing",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "processNext",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsICycleCollectorHandler",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden",
+ "hasretval"
+ ],
+ "name": "asLogger",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsICycleCollectorListener",
+ "parent": "nsISupports",
+ "uuid": "703b53b6-24f6-40c6-9ea9-aeb2dc53d170"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isDebugBuild",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "assertionCount",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isDebuggerAttached",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "assertion",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "warning",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "break",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "abort",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "rustPanic",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "rustLog",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_PSTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "crashWithOOM",
+ "params": []
+ }
+ ],
+ "name": "nsIDebug2",
+ "parent": "nsISupports",
+ "uuid": "9641dc15-10fb-42e3-a285-18be90a5c10b"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "filename",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "name",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "sourceId",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "lineNumber",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "columnNumber",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "sourceLine",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "asyncCause",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "asyncCaller",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIStackFrame",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "caller",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIStackFrame",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "formattedStack",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "nativeSavedFrame",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "toString",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getFilename",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getName",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getSourceId",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getLineNumber",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getColumnNumber",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getAsyncCause",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getAsyncCaller",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getCaller",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getFormattedStack",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "toStringInfallible",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIStackFrame",
+ "parent": "nsISupports",
+ "uuid": "28bfb2a2-5ea6-4738-918b-049dc4d51f0b"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [],
+ "name": "nsIException",
+ "parent": "nsISupports",
+ "uuid": "4371b5bf-6845-487f-8d9d-3f1e4a9badd2"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "init",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIFile",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "initANSIFileDesc",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "write",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "finish",
+ "params": []
+ }
+ ],
+ "name": "nsIGZFileWriter",
+ "parent": "nsISupports",
+ "uuid": "6bd5642c-1b90-4499-ba4b-199f27efaba5"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "getInterface",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_NSID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "iid_is": 0,
+ "tag": "TD_INTERFACE_IS_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIInterfaceRequestor",
+ "parent": "nsISupports",
+ "uuid": "033a1470-8b2a-11d3-af88-00a024ffc08c"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "policiesEnabled",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "jscontext",
+ "hasretval"
+ ],
+ "name": "readPreferences",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_JSVAL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIMacPreferencesReader",
+ "parent": "nsISupports",
+ "uuid": "b0f20595-88ce-4738-a1a4-24de78eb8051"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "architecturesInBinary",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isTranslated",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIMacUtils",
+ "parent": "nsISupports",
+ "uuid": "5e9072d7-ff95-455e-9466-8af9841a72ec"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "heapMinimize",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "isLowMemoryPlatform",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIMemory",
+ "parent": "nsISupports",
+ "uuid": "1e004834-6d8f-425a-bc9c-a2812ed43bb7"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "function"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "callback",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIFinishDumpingCallback",
+ "parent": "nsISupports",
+ "uuid": "2dea18fc-fbfa-4bf7-ad45-0efaf5495f5e"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "onDump",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIFile",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIFile",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "onFinish",
+ "params": []
+ }
+ ],
+ "name": "nsIDumpGCAndCCLogsCallback",
+ "parent": "nsISupports",
+ "uuid": "dc1b2b24-65bd-441b-b6bd-cb5825a7ed14"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "dumpMemoryReportsToNamedFile",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIFinishDumpingCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "dumpMemoryInfoToTempDir",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "dumpGCAndCCLogsToFile",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIDumpGCAndCCLogsCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "dumpGCAndCCLogsToSink",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsICycleCollectorLogSink",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIMemoryInfoDumper",
+ "parent": "nsISupports",
+ "uuid": "48541b74-47ee-4a62-9557-7f4b809bda5c"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "function"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "callback",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UTF8STRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIHandleReportCallback",
+ "parent": "nsISupports",
+ "uuid": "62ef0e1c-dbd6-11e3-aa75-3c970e9f4238"
+ },
+ {
+ "consts": [
+ {
+ "name": "KIND_NONHEAP",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 0
+ },
+ {
+ "name": "KIND_HEAP",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "KIND_OTHER",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "UNITS_BYTES",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 0
+ },
+ {
+ "name": "UNITS_COUNT",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 1
+ },
+ {
+ "name": "UNITS_COUNT_CUMULATIVE",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 2
+ },
+ {
+ "name": "UNITS_PERCENTAGE",
+ "type": {
+ "tag": "TD_INT32"
+ },
+ "value": 3
+ }
+ ],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "collectReports",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIHandleReportCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIMemoryReporter",
+ "parent": "nsISupports",
+ "uuid": "92a36db1-46bd-4fe6-988e-47db47236d8b"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "function"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "callback",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIFinishReportingCallback",
+ "parent": "nsISupports",
+ "uuid": "548b3909-c04d-4ca6-8466-b8bee3837457"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "function"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "callback",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIHeapAllocatedCallback",
+ "parent": "nsISupports",
+ "uuid": "1a80cd0f-0d9e-4397-be69-68ad28fe5175"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [],
+ "name": "init",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "registerStrongReporter",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIMemoryReporter",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "registerStrongAsyncReporter",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIMemoryReporter",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "registerWeakReporter",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIMemoryReporter",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "registerWeakAsyncReporter",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIMemoryReporter",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "unregisterStrongReporter",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIMemoryReporter",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "unregisterWeakReporter",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIMemoryReporter",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "blockRegistrationAndHideExistingReporters",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "unblockRegistrationAndRestoreOriginalReporters",
+ "params": []
+ },
+ {
+ "flags": [],
+ "name": "registerStrongReporterEvenIfBlocked",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIMemoryReporter",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "getReports",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIHandleReportCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIFinishReportingCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getReportsExtended",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIHandleReportCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIFinishReportingCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_ASTRING"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "getReportsForThisProcessExtended",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIHandleReportCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIFinishReportingCallback",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsISupports",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "endReport",
+ "params": []
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "vsize",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "vsizeMaxContiguous",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "resident",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "residentFast",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "residentPeak",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "residentUnique",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "heapAllocated",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "heapOverheadFraction",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "JSMainRuntimeGCHeap",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "JSMainRuntimeTemporaryPeak",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "JSMainRuntimeCompartmentsSystem",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "JSMainRuntimeCompartmentsUser",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "JSMainRuntimeRealmsSystem",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "JSMainRuntimeRealmsUser",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "imagesContentUsedUncompressed",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "storageSQLite",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "lowMemoryEventsPhysical",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "ghostWindows",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "pageFaultsHard",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "hasMozMallocUsableSize",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isDMDEnabled",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "getter",
+ "hasretval"
+ ],
+ "name": "isDMDRunning",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_BOOL"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "minimizeMemoryUsage",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIRunnable",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [],
+ "name": "sizeOfTab",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "mozIDOMWindowProxy",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT64"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_DOUBLE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIMemoryReporterManager",
+ "parent": "nsISupports",
+ "uuid": "2998574d-8993-407a-b1a5-8ad7417653e1"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [],
+ "name": "postIdleTask",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "name": "nsIRunnable",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_UINT32"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIMessageLoop",
+ "parent": "nsISupports",
+ "uuid": "3e8c58e8-e52c-43e0-8e66-669ca788ff5f"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "QueryInterface",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_NSID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "iid_is": 0,
+ "tag": "TD_INTERFACE_IS_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "AddRef",
+ "params": []
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "Release",
+ "params": []
+ }
+ ],
+ "name": "nsISupports",
+ "parent": null,
+ "uuid": "00000000-0000-0000-c000-000000000046"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "generateUUID",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_NSIDPTR"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "generateUUIDInPlace",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIUUIDGenerator",
+ "parent": "nsISupports",
+ "uuid": "138ad1b2-c694-41cc-b201-333ce936d8b8"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "compare",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_CSTRING"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "tag": "TD_INT32"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIVersionComparator",
+ "parent": "nsISupports",
+ "uuid": "e6cd620a-edbb-41d2-9e42-9a2ffc8107f3"
+ },
+ {
+ "consts": [],
+ "flags": [
+ "builtinclass"
+ ],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "QueryReferent",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_NSID"
+ }
+ },
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "iid_is": 0,
+ "tag": "TD_INTERFACE_IS_TYPE"
+ }
+ }
+ ]
+ },
+ {
+ "flags": [
+ "hidden"
+ ],
+ "name": "sizeOfOnlyThis",
+ "params": [
+ {
+ "flags": [
+ "in"
+ ],
+ "type": {
+ "tag": "TD_VOID"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsIWeakReference",
+ "parent": "nsISupports",
+ "uuid": "9188bc85-f92e-11d2-81ef-0060083a0bcf"
+ },
+ {
+ "consts": [],
+ "flags": [],
+ "methods": [
+ {
+ "flags": [
+ "hasretval"
+ ],
+ "name": "GetWeakReference",
+ "params": [
+ {
+ "flags": [
+ "out"
+ ],
+ "type": {
+ "name": "nsIWeakReference",
+ "tag": "TD_INTERFACE_TYPE"
+ }
+ }
+ ]
+ }
+ ],
+ "name": "nsISupportsWeakReference",
+ "parent": "nsISupports",
+ "uuid": "9188bc86-f92e-11d2-81ef-0060083a0bcf"
+ }
+] \ No newline at end of file
diff --git a/tools/lint/eslint/eslint-plugin-mozilla/update.sh b/tools/lint/eslint/eslint-plugin-mozilla/update.sh
new file mode 100755
index 0000000000..238e7695b1
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-mozilla/update.sh
@@ -0,0 +1,61 @@
+#!/bin/sh
+# Script to regenerate the npm packages used for eslint-plugin-mozilla by the builders.
+# Requires
+
+# Force the scripts working directory to be projdir/tools/lint/eslint/eslint-plugin-mozilla.
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd $DIR
+
+echo "To complete this script you will need the following tokens from https://mozilla-releng.net/tokens"
+echo " - tooltool.upload.public"
+echo " - tooltool.download.public"
+echo ""
+read -p "Are these tokens visible at the above URL (y/n)?" choice
+case "$choice" in
+ y|Y )
+ echo ""
+ echo "1. Go to https://mozilla-releng.net"
+ echo "2. Log in using your Mozilla LDAP account."
+ echo "3. Click on \"Tokens.\""
+ echo "4. Issue a user token with the permissions tooltool.upload.public and tooltool.download.public."
+ echo ""
+ echo "When you click issue you will be presented with a long string. Paste the string into a temporary file called ~/.tooltool-token."
+ echo ""
+ read -rsp $'Press any key to continue...\n' -n 1
+ ;;
+ n|N )
+ echo ""
+ echo "You will need to contact somebody that has these permissions... people most likely to have these permissions are members of the releng, ateam, a sheriff, mratcliffe, or jryans"
+ exit 1
+ ;;
+ * )
+ echo ""
+ echo "Invalid input."
+ continue
+ ;;
+esac
+
+echo ""
+echo "Removing node_modules and package-lock.json..."
+# Move to the top-level directory.
+rm -rf node_modules
+rm package-lock.json
+
+echo "Installing modules for eslint-plugin-mozilla..."
+../../../../mach npm install
+
+echo "Creating eslint-plugin-mozilla.tar.gz..."
+tar cvz -f eslint-plugin-mozilla.tar.gz node_modules
+
+echo "Adding eslint-plugin-mozilla.tar.gz to tooltool..."
+rm -f manifest.tt
+../../../../python/mozbuild/mozbuild/action/tooltool.py add --visibility public --unpack eslint-plugin-mozilla.tar.gz --url="https://tooltool.mozilla-releng.net/"
+
+echo "Uploading eslint-plugin-mozilla.tar.gz to tooltool..."
+../../../../python/mozbuild/mozbuild/action/tooltool.py upload --authentication-file=~/.tooltool-token --message "node_modules folder update for tools/lint/eslint/eslint-plugin-mozilla" --url="https://tooltool.mozilla-releng.net/"
+
+echo "Cleaning up..."
+rm eslint-plugin-mozilla.tar.gz
+
+echo ""
+echo "Update complete, please commit and check in your changes."
diff --git a/tools/lint/eslint/eslint-plugin-spidermonkey-js/LICENSE b/tools/lint/eslint/eslint-plugin-spidermonkey-js/LICENSE
new file mode 100644
index 0000000000..e87a115e46
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-spidermonkey-js/LICENSE
@@ -0,0 +1,363 @@
+Mozilla Public License, version 2.0
+
+1. Definitions
+
+1.1. "Contributor"
+
+ means each individual or legal entity that creates, contributes to the
+ creation of, or owns Covered Software.
+
+1.2. "Contributor Version"
+
+ means the combination of the Contributions of others (if any) used by a
+ Contributor and that particular Contributor's Contribution.
+
+1.3. "Contribution"
+
+ means Covered Software of a particular Contributor.
+
+1.4. "Covered Software"
+
+ means Source Code Form to which the initial Contributor has attached the
+ notice in Exhibit A, the Executable Form of such Source Code Form, and
+ Modifications of such Source Code Form, in each case including portions
+ thereof.
+
+1.5. "Incompatible With Secondary Licenses"
+ means
+
+ a. that the initial Contributor has attached the notice described in
+ Exhibit B to the Covered Software; or
+
+ b. that the Covered Software was made available under the terms of
+ version 1.1 or earlier of the License, but not also under the terms of
+ a Secondary License.
+
+1.6. "Executable Form"
+
+ means any form of the work other than Source Code Form.
+
+1.7. "Larger Work"
+
+ means a work that combines Covered Software with other material, in a
+ separate file or files, that is not Covered Software.
+
+1.8. "License"
+
+ means this document.
+
+1.9. "Licensable"
+
+ means having the right to grant, to the maximum extent possible, whether
+ at the time of the initial grant or subsequently, any and all of the
+ rights conveyed by this License.
+
+1.10. "Modifications"
+
+ means any of the following:
+
+ a. any file in Source Code Form that results from an addition to,
+ deletion from, or modification of the contents of Covered Software; or
+
+ b. any new file in Source Code Form that contains any Covered Software.
+
+1.11. "Patent Claims" of a Contributor
+
+ means any patent claim(s), including without limitation, method,
+ process, and apparatus claims, in any patent Licensable by such
+ Contributor that would be infringed, but for the grant of the License,
+ by the making, using, selling, offering for sale, having made, import,
+ or transfer of either its Contributions or its Contributor Version.
+
+1.12. "Secondary License"
+
+ means either the GNU General Public License, Version 2.0, the GNU Lesser
+ General Public License, Version 2.1, the GNU Affero General Public
+ License, Version 3.0, or any later versions of those licenses.
+
+1.13. "Source Code Form"
+
+ means the form of the work preferred for making modifications.
+
+1.14. "You" (or "Your")
+
+ means an individual or a legal entity exercising rights under this
+ License. For legal entities, "You" includes any entity that controls, is
+ controlled by, or is under common control with You. For purposes of this
+ definition, "control" means (a) the power, direct or indirect, to cause
+ the direction or management of such entity, whether by contract or
+ otherwise, or (b) ownership of more than fifty percent (50%) of the
+ outstanding shares or beneficial ownership of such entity.
+
+
+2. License Grants and Conditions
+
+2.1. Grants
+
+ Each Contributor hereby grants You a world-wide, royalty-free,
+ non-exclusive license:
+
+ a. under intellectual property rights (other than patent or trademark)
+ Licensable by such Contributor to use, reproduce, make available,
+ modify, display, perform, distribute, and otherwise exploit its
+ Contributions, either on an unmodified basis, with Modifications, or
+ as part of a Larger Work; and
+
+ b. under Patent Claims of such Contributor to make, use, sell, offer for
+ sale, have made, import, and otherwise transfer either its
+ Contributions or its Contributor Version.
+
+2.2. Effective Date
+
+ The licenses granted in Section 2.1 with respect to any Contribution
+ become effective for each Contribution on the date the Contributor first
+ distributes such Contribution.
+
+2.3. Limitations on Grant Scope
+
+ The licenses granted in this Section 2 are the only rights granted under
+ this License. No additional rights or licenses will be implied from the
+ distribution or licensing of Covered Software under this License.
+ Notwithstanding Section 2.1(b) above, no patent license is granted by a
+ Contributor:
+
+ a. for any code that a Contributor has removed from Covered Software; or
+
+ b. for infringements caused by: (i) Your and any other third party's
+ modifications of Covered Software, or (ii) the combination of its
+ Contributions with other software (except as part of its Contributor
+ Version); or
+
+ c. under Patent Claims infringed by Covered Software in the absence of
+ its Contributions.
+
+ This License does not grant any rights in the trademarks, service marks,
+ or logos of any Contributor (except as may be necessary to comply with
+ the notice requirements in Section 3.4).
+
+2.4. Subsequent Licenses
+
+ No Contributor makes additional grants as a result of Your choice to
+ distribute the Covered Software under a subsequent version of this
+ License (see Section 10.2) or under the terms of a Secondary License (if
+ permitted under the terms of Section 3.3).
+
+2.5. Representation
+
+ Each Contributor represents that the Contributor believes its
+ Contributions are its original creation(s) or it has sufficient rights to
+ grant the rights to its Contributions conveyed by this License.
+
+2.6. Fair Use
+
+ This License is not intended to limit any rights You have under
+ applicable copyright doctrines of fair use, fair dealing, or other
+ equivalents.
+
+2.7. Conditions
+
+ Sections 3.1, 3.2, 3.3, and 3.4 are conditions of the licenses granted in
+ Section 2.1.
+
+
+3. Responsibilities
+
+3.1. Distribution of Source Form
+
+ All distribution of Covered Software in Source Code Form, including any
+ Modifications that You create or to which You contribute, must be under
+ the terms of this License. You must inform recipients that the Source
+ Code Form of the Covered Software is governed by the terms of this
+ License, and how they can obtain a copy of this License. You may not
+ attempt to alter or restrict the recipients' rights in the Source Code
+ Form.
+
+3.2. Distribution of Executable Form
+
+ If You distribute Covered Software in Executable Form then:
+
+ a. such Covered Software must also be made available in Source Code Form,
+ as described in Section 3.1, and You must inform recipients of the
+ Executable Form how they can obtain a copy of such Source Code Form by
+ reasonable means in a timely manner, at a charge no more than the cost
+ of distribution to the recipient; and
+
+ b. You may distribute such Executable Form under the terms of this
+ License, or sublicense it under different terms, provided that the
+ license for the Executable Form does not attempt to limit or alter the
+ recipients' rights in the Source Code Form under this License.
+
+3.3. Distribution of a Larger Work
+
+ You may create and distribute a Larger Work under terms of Your choice,
+ provided that You also comply with the requirements of this License for
+ the Covered Software. If the Larger Work is a combination of Covered
+ Software with a work governed by one or more Secondary Licenses, and the
+ Covered Software is not Incompatible With Secondary Licenses, this
+ License permits You to additionally distribute such Covered Software
+ under the terms of such Secondary License(s), so that the recipient of
+ the Larger Work may, at their option, further distribute the Covered
+ Software under the terms of either this License or such Secondary
+ License(s).
+
+3.4. Notices
+
+ You may not remove or alter the substance of any license notices
+ (including copyright notices, patent notices, disclaimers of warranty, or
+ limitations of liability) contained within the Source Code Form of the
+ Covered Software, except that You may alter any license notices to the
+ extent required to remedy known factual inaccuracies.
+
+3.5. Application of Additional Terms
+
+ You may choose to offer, and to charge a fee for, warranty, support,
+ indemnity or liability obligations to one or more recipients of Covered
+ Software. However, You may do so only on Your own behalf, and not on
+ behalf of any Contributor. You must make it absolutely clear that any
+ such warranty, support, indemnity, or liability obligation is offered by
+ You alone, and You hereby agree to indemnify every Contributor for any
+ liability incurred by such Contributor as a result of warranty, support,
+ indemnity or liability terms You offer. You may include additional
+ disclaimers of warranty and limitations of liability specific to any
+ jurisdiction.
+
+4. Inability to Comply Due to Statute or Regulation
+
+ If it is impossible for You to comply with any of the terms of this License
+ with respect to some or all of the Covered Software due to statute,
+ judicial order, or regulation then You must: (a) comply with the terms of
+ this License to the maximum extent possible; and (b) describe the
+ limitations and the code they affect. Such description must be placed in a
+ text file included with all distributions of the Covered Software under
+ this License. Except to the extent prohibited by statute or regulation,
+ such description must be sufficiently detailed for a recipient of ordinary
+ skill to be able to understand it.
+
+5. Termination
+
+5.1. The rights granted under this License will terminate automatically if You
+ fail to comply with any of its terms. However, if You become compliant,
+ then the rights granted under this License from a particular Contributor
+ are reinstated (a) provisionally, unless and until such Contributor
+ explicitly and finally terminates Your grants, and (b) on an ongoing
+ basis, if such Contributor fails to notify You of the non-compliance by
+ some reasonable means prior to 60 days after You have come back into
+ compliance. Moreover, Your grants from a particular Contributor are
+ reinstated on an ongoing basis if such Contributor notifies You of the
+ non-compliance by some reasonable means, this is the first time You have
+ received notice of non-compliance with this License from such
+ Contributor, and You become compliant prior to 30 days after Your receipt
+ of the notice.
+
+5.2. If You initiate litigation against any entity by asserting a patent
+ infringement claim (excluding declaratory judgment actions,
+ counter-claims, and cross-claims) alleging that a Contributor Version
+ directly or indirectly infringes any patent, then the rights granted to
+ You by any and all Contributors for the Covered Software under Section
+ 2.1 of this License shall terminate.
+
+5.3. In the event of termination under Sections 5.1 or 5.2 above, all end user
+ license agreements (excluding distributors and resellers) which have been
+ validly granted by You or Your distributors under this License prior to
+ termination shall survive termination.
+
+6. Disclaimer of Warranty
+
+ Covered Software is provided under this License on an "as is" basis,
+ without warranty of any kind, either expressed, implied, or statutory,
+ including, without limitation, warranties that the Covered Software is free
+ of defects, merchantable, fit for a particular purpose or non-infringing.
+ The entire risk as to the quality and performance of the Covered Software
+ is with You. Should any Covered Software prove defective in any respect,
+ You (not any Contributor) assume the cost of any necessary servicing,
+ repair, or correction. This disclaimer of warranty constitutes an essential
+ part of this License. No use of any Covered Software is authorized under
+ this License except under this disclaimer.
+
+7. Limitation of Liability
+
+ Under no circumstances and under no legal theory, whether tort (including
+ negligence), contract, or otherwise, shall any Contributor, or anyone who
+ distributes Covered Software as permitted above, be liable to You for any
+ direct, indirect, special, incidental, or consequential damages of any
+ character including, without limitation, damages for lost profits, loss of
+ goodwill, work stoppage, computer failure or malfunction, or any and all
+ other commercial damages or losses, even if such party shall have been
+ informed of the possibility of such damages. This limitation of liability
+ shall not apply to liability for death or personal injury resulting from
+ such party's negligence to the extent applicable law prohibits such
+ limitation. Some jurisdictions do not allow the exclusion or limitation of
+ incidental or consequential damages, so this exclusion and limitation may
+ not apply to You.
+
+8. Litigation
+
+ Any litigation relating to this License may be brought only in the courts
+ of a jurisdiction where the defendant maintains its principal place of
+ business and such litigation shall be governed by laws of that
+ jurisdiction, without reference to its conflict-of-law provisions. Nothing
+ in this Section shall prevent a party's ability to bring cross-claims or
+ counter-claims.
+
+9. Miscellaneous
+
+ This License represents the complete agreement concerning the subject
+ matter hereof. If any provision of this License is held to be
+ unenforceable, such provision shall be reformed only to the extent
+ necessary to make it enforceable. Any law or regulation which provides that
+ the language of a contract shall be construed against the drafter shall not
+ be used to construe this License against a Contributor.
+
+
+10. Versions of the License
+
+10.1. New Versions
+
+ Mozilla Foundation is the license steward. Except as provided in Section
+ 10.3, no one other than the license steward has the right to modify or
+ publish new versions of this License. Each version will be given a
+ distinguishing version number.
+
+10.2. Effect of New Versions
+
+ You may distribute the Covered Software under the terms of the version
+ of the License under which You originally received the Covered Software,
+ or under the terms of any subsequent version published by the license
+ steward.
+
+10.3. Modified Versions
+
+ If you create software not governed by this License, and you want to
+ create a new license for such software, you may create and use a
+ modified version of this License if you rename the license and remove
+ any references to the name of the license steward (except to note that
+ such modified license differs from this License).
+
+10.4. Distributing Source Code Form that is Incompatible With Secondary
+ Licenses If You choose to distribute Source Code Form that is
+ Incompatible With Secondary Licenses under the terms of this version of
+ the License, the notice described in Exhibit B of this License must be
+ attached.
+
+Exhibit A - Source Code Form License Notice
+
+ 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/.
+
+If it is not possible or desirable to put the notice in a particular file,
+then You may include the notice in a location (such as a LICENSE file in a
+relevant directory) where a recipient would be likely to look for such a
+notice.
+
+You may add additional accurate notices of copyright ownership.
+
+Exhibit B - "Incompatible With Secondary Licenses" Notice
+
+ This Source Code Form is "Incompatible
+ With Secondary Licenses", as defined by
+ the Mozilla Public License, v. 2.0.
+
diff --git a/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/environments/self-hosted.js b/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/environments/self-hosted.js
new file mode 100644
index 0000000000..37ae42bfa3
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/environments/self-hosted.js
@@ -0,0 +1,180 @@
+/**
+ * @fileoverview Add environment defaults to SpiderMonkey's self-hosted JS.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const path = require("path");
+const fs = require("fs");
+
+let gRootDir = null;
+
+// Copied from `tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js`.
+function getRootDir() {
+ if (!gRootDir) {
+ function searchUpForIgnore(dirName, filename) {
+ let parsed = path.parse(dirName);
+ while (parsed.root !== dirName) {
+ if (fs.existsSync(path.join(dirName, filename))) {
+ return dirName;
+ }
+ // Move up a level
+ dirName = parsed.dir;
+ parsed = path.parse(dirName);
+ }
+ return null;
+ }
+
+ let possibleRoot = searchUpForIgnore(
+ path.dirname(module.filename),
+ ".eslintignore"
+ );
+ if (!possibleRoot) {
+ possibleRoot = searchUpForIgnore(path.resolve(), ".eslintignore");
+ }
+ if (!possibleRoot) {
+ possibleRoot = searchUpForIgnore(path.resolve(), "package.json");
+ }
+ if (!possibleRoot) {
+ // We've couldn't find a root from the module or CWD, so lets just go
+ // for the CWD. We really don't want to throw if possible, as that
+ // tends to give confusing results when used with ESLint.
+ possibleRoot = process.cwd();
+ }
+
+ gRootDir = possibleRoot;
+ }
+
+ return gRootDir;
+}
+
+function tryReadFile(filePath) {
+ let absPath = path.join(getRootDir(), filePath);
+ if (!fs.existsSync(absPath)) {
+ // Safely handle the case when the file wasn't found, because throwing
+ // errors can lead to confusing result when used with ESLint.
+ return "";
+ }
+ return fs.readFileSync(absPath, "utf-8");
+}
+
+// Search for top-level declarations, #defines, and #includes.
+function addGlobalsFrom(dirName, fileName, globals) {
+ let filePath = path.join(dirName, fileName);
+
+ // Definitions are separated by line.
+ let lines = tryReadFile(filePath).split("\n");
+
+ // We don't have to fully parse the source code, because it's formatted
+ // through "prettier", which means we know the exact code structure.
+ //
+ // |class| is disallowed in self-hosted code, so we don't have to handle it.
+ for (let line of lines) {
+ if (
+ line.startsWith("function") ||
+ line.startsWith("function*") ||
+ line.startsWith("async function") ||
+ line.startsWith("async function*")
+ ) {
+ let m = line.match(/^(?:async )?function(?:\*)?\s+([\w\$]+)\s*\(/);
+ if (m) {
+ globals[m[1]] = "readonly";
+ }
+ } else if (
+ line.startsWith("var") ||
+ line.startsWith("let") ||
+ line.startsWith("const")
+ ) {
+ let m = line.match(/^(?:var|let|const)\s+([\w\$]+)\s*[;=]/);
+ if (m) {
+ globals[m[1]] = "readonly";
+ }
+ } else if (line.startsWith("#define")) {
+ let m = line.match(/^#define (\w+)/);
+ if (m) {
+ globals[m[1]] = "readonly";
+ }
+ } else if (line.startsWith("#include")) {
+ let m = line.match(/^#include \"([\w\.]+)\"$/);
+ if (m) {
+ // Also process definitions from includes.
+ addGlobalsFrom(dirName, m[1], globals);
+ }
+ }
+ }
+}
+
+function selfHostingDefines(dirName = "js/src/builtin/") {
+ let absDir = path.join(getRootDir(), dirName);
+ if (!fs.existsSync(absDir)) {
+ // See |tryReadFile| for why we avoid to throw any errors.
+ return {};
+ }
+
+ // Search sub-directories and js-files within |dirName|.
+ let dirs = [];
+ let jsFiles = [];
+ for (let name of fs.readdirSync(absDir)) {
+ let stat = fs.statSync(path.join(absDir, name));
+ if (stat.isDirectory()) {
+ dirs.push(name);
+ } else if (stat.isFile() && name.endsWith(".js")) {
+ jsFiles.push(name);
+ }
+ }
+
+ let globals = Object.create(null);
+
+ // Process each js-file.
+ for (let jsFile of jsFiles) {
+ addGlobalsFrom(dirName, jsFile, globals);
+ }
+
+ // Recursively traverse all sub-directories.
+ for (let dir of dirs) {
+ globals = { ...globals, ...selfHostingDefines(path.join(dirName, dir)) };
+ }
+
+ return globals;
+}
+
+function selfHostingFunctions() {
+ // Definitions can be spread across multiple lines and may have extra
+ // whitespace, so we simply remove all whitespace and match over the complete
+ // file.
+ let content = tryReadFile("js/src/vm/SelfHosting.cpp").replace(/\s+/g, "");
+
+ let globals = Object.create(null);
+ for (let m of content.matchAll(/(?:JS_FN|JS_INLINABLE_FN)\("(\w+)"/g)) {
+ globals[m[1]] = "readonly";
+ }
+ return globals;
+}
+
+function errorNumbers() {
+ // Definitions are separated by line.
+ let lines = tryReadFile("js/public/friend/ErrorNumbers.msg").split("\n");
+
+ let globals = Object.create(null);
+ for (let line of lines) {
+ let m = line.match(/^MSG_DEF\((\w+),/);
+ if (m) {
+ globals[m[1]] = "readonly";
+ }
+ }
+ return globals;
+}
+
+const globals = {
+ ...selfHostingDefines(),
+ ...selfHostingFunctions(),
+ ...errorNumbers(),
+};
+
+module.exports = {
+ globals,
+};
diff --git a/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/index.js b/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/index.js
new file mode 100644
index 0000000000..d9d40af8c6
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/index.js
@@ -0,0 +1,20 @@
+/**
+ * @fileoverview A processor to help parse the spidermonkey js code.
+ * 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/.
+ */
+
+"use strict";
+
+// ------------------------------------------------------------------------------
+// Plugin Definition
+// ------------------------------------------------------------------------------
+module.exports = {
+ processors: {
+ processor: require("../lib/processors/self-hosted"),
+ },
+ environments: {
+ environment: require("../lib/environments/self-hosted"),
+ },
+};
diff --git a/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/processors/self-hosted.js b/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/processors/self-hosted.js
new file mode 100644
index 0000000000..96adade93a
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/processors/self-hosted.js
@@ -0,0 +1,128 @@
+/**
+ * @fileoverview Remove macros from SpiderMonkey's self-hosted JS.
+ *
+ * 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/.
+ */
+
+"use strict";
+
+const path = require("path");
+const fs = require("fs");
+
+const selfHostedRegex = /js\/src\/builtin\/.*?\.js$/;
+const macroRegex = /\s*\#(if|ifdef|else|elif|endif|include|define|undef).*/;
+
+function isSelfHostedFile(filename) {
+ if (path.win32) {
+ filename = filename.split(path.sep).join("/");
+ }
+ return selfHostedRegex.test(filename);
+}
+
+function tryReadFile(filePath) {
+ if (!path.isAbsolute(filePath)) {
+ return "";
+ }
+ if (!fs.existsSync(filePath)) {
+ // Safely handle the case when the file wasn't found, because throwing
+ // errors can lead to confusing result when used with ESLint.
+ return "";
+ }
+ return fs.readFileSync(filePath, "utf-8");
+}
+
+// Adjust the range of fixes to match the original source code.
+function createFix(lines, message) {
+ let { line, column, fix } = message;
+
+ // Line and column are 1-based. Make sure we got a valid input.
+ if (line <= 0 || column <= 0) {
+ return null;
+ }
+
+ // Reject to create a fix when the line is out of range for some reason.
+ if (line > lines.length) {
+ return null;
+ }
+
+ // Find the absolute start position of the line in the original file.
+ let startOfLine = 0;
+ for (let i = 0; i < line - 1; ++i) {
+ // Add the length of the line, including its line separator.
+ startOfLine += lines[i].length + "\n".length;
+ }
+
+ // Add the 1-based column to the start of line to get the start position.
+ let start = startOfLine + (column - 1);
+
+ // Add the fix range to get the end position.
+ let end = start + (fix.range[1] - fix.range[0]);
+
+ // And finally return the new fix object.
+ return { text: fix.text, range: [start, end] };
+}
+
+module.exports = {
+ preprocess(text, filename) {
+ if (!isSelfHostedFile(filename)) {
+ return [text];
+ }
+
+ let lines = text.split(/\n/);
+ for (let i = 0; i < lines.length; i++) {
+ if (!macroRegex.test(lines[i])) {
+ // No macro here, nothing to do.
+ continue;
+ }
+
+ for (; i < lines.length; i++) {
+ // The macro isn't correctly indented, so we need to instruct
+ // prettier to ignore them.
+ lines[i] = "// eslint-disable-line prettier/prettier -- " + lines[i];
+
+ // If the line ends with a backslash (\), the next line
+ // is also part of part of the macro.
+ if (!lines[i].endsWith("\\")) {
+ break;
+ }
+ }
+ }
+
+ return [lines.join("\n")];
+ },
+
+ postprocess(messages, filename) {
+ // Don't attempt to create fixes for any non-selfhosted files.
+ if (!isSelfHostedFile(filename)) {
+ return [].concat(...messages);
+ }
+
+ let lines = null;
+
+ let result = [];
+ for (let message of messages.flat()) {
+ if (message.fix) {
+ if (lines === null) {
+ lines = tryReadFile(filename).split(/\n/);
+ }
+
+ let fix = createFix(lines, message);
+ if (fix) {
+ message.fix = fix;
+ } else {
+ // We couldn't create a fix, so we better remove the passed in fix,
+ // because its range points to the preprocessor output, but the post-
+ // processor must translate it into a range of the original source.
+ delete message.fix;
+ }
+ }
+
+ result.push(message);
+ }
+ return result;
+ },
+
+ supportsAutofix: true,
+};
diff --git a/tools/lint/eslint/eslint-plugin-spidermonkey-js/package.json b/tools/lint/eslint/eslint-plugin-spidermonkey-js/package.json
new file mode 100644
index 0000000000..61452d9e96
--- /dev/null
+++ b/tools/lint/eslint/eslint-plugin-spidermonkey-js/package.json
@@ -0,0 +1,30 @@
+{
+ "name": "eslint-plugin-spidermonkey-js",
+ "version": "0.1.1",
+ "description": "A collection of rules that help enforce JavaScript coding standard in the Mozilla SpiderMonkey project.",
+ "keywords": [
+ "eslint",
+ "eslintplugin",
+ "eslint-plugin",
+ "mozilla",
+ "spidermonkey"
+ ],
+ "bugs": {
+ "url": "https://bugzilla.mozilla.org/enter_bug.cgi?product=Testing&component=Lint"
+ },
+ "homepage": "http://firefox-source-docs.mozilla.org/tools/lint/linters/eslint-plugin-spidermonkey-js.html",
+ "repository": {
+ "type": "hg",
+ "url": "https://hg.mozilla.org/mozilla-central/"
+ },
+ "author": "Mozilla",
+ "main": "lib/index.js",
+ "dependencies": {
+ },
+ "devDependencies": {
+ },
+ "engines": {
+ "node": ">=6.9.1"
+ },
+ "license": "MPL-2.0"
+}
diff --git a/tools/lint/eslint/manifest.tt b/tools/lint/eslint/manifest.tt
new file mode 100644
index 0000000000..9992e902f5
--- /dev/null
+++ b/tools/lint/eslint/manifest.tt
@@ -0,0 +1,10 @@
+[
+ {
+ "filename": "eslint.tar.gz",
+ "size": 28525866,
+ "algorithm": "sha512",
+ "digest": "969433e493a6f5dacff4957478c4203b4a90abe7db762c890231fba16614e3eb3a7d1a956542a465bd2eefb996214fa176e95c885bcfce4daa1af963ab879aa1",
+ "unpack": true,
+ "visibility": "public"
+ }
+] \ No newline at end of file
diff --git a/tools/lint/eslint/setup_helper.py b/tools/lint/eslint/setup_helper.py
new file mode 100644
index 0000000000..0e72a16c3b
--- /dev/null
+++ b/tools/lint/eslint/setup_helper.py
@@ -0,0 +1,421 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 json
+import os
+import platform
+import re
+import subprocess
+import sys
+from filecmp import dircmp
+
+from mozbuild.nodeutil import (
+ NODE_MIN_VERSION,
+ NPM_MIN_VERSION,
+ find_node_executable,
+ find_npm_executable,
+)
+from mozfile.mozfile import remove as mozfileremove
+from packaging.version import Version
+
+NODE_MACHING_VERSION_NOT_FOUND_MESSAGE = """
+Could not find Node.js executable later than %s.
+
+Executing `mach bootstrap --no-system-changes` should
+install a compatible version in ~/.mozbuild on most platforms.
+""".strip()
+
+NPM_MACHING_VERSION_NOT_FOUND_MESSAGE = """
+Could not find npm executable later than %s.
+
+Executing `mach bootstrap --no-system-changes` should
+install a compatible version in ~/.mozbuild on most platforms.
+""".strip()
+
+NODE_NOT_FOUND_MESSAGE = """
+nodejs is either not installed or is installed to a non-standard path.
+
+Executing `mach bootstrap --no-system-changes` should
+install a compatible version in ~/.mozbuild on most platforms.
+""".strip()
+
+NPM_NOT_FOUND_MESSAGE = """
+Node Package Manager (npm) is either not installed or installed to a
+non-standard path.
+
+Executing `mach bootstrap --no-system-changes` should
+install a compatible version in ~/.mozbuild on most platforms.
+""".strip()
+
+
+VERSION_RE = re.compile(r"^\d+\.\d+\.\d+$")
+CARET_VERSION_RANGE_RE = re.compile(r"^\^((\d+)\.\d+\.\d+)$")
+
+project_root = None
+
+
+def eslint_maybe_setup():
+ """Setup ESLint only if it is needed."""
+ has_issues, needs_clobber = eslint_module_needs_setup()
+
+ if has_issues:
+ eslint_setup(needs_clobber)
+
+
+def eslint_setup(should_clobber=False):
+ """Ensure eslint is optimally configured.
+
+ This command will inspect your eslint configuration and
+ guide you through an interactive wizard helping you configure
+ eslint for optimal use on Mozilla projects.
+ """
+ package_setup(get_project_root(), "eslint", should_clobber=should_clobber)
+
+
+def package_setup(
+ package_root,
+ package_name,
+ should_update=False,
+ should_clobber=False,
+ no_optional=False,
+):
+ """Ensure `package_name` at `package_root` is installed.
+
+ When `should_update` is true, clobber, install, and produce a new
+ "package-lock.json" file.
+
+ This populates `package_root/node_modules`.
+
+ """
+ orig_project_root = get_project_root()
+ orig_cwd = os.getcwd()
+
+ if should_update:
+ should_clobber = True
+
+ try:
+ set_project_root(package_root)
+ sys.path.append(os.path.dirname(__file__))
+
+ # npm sometimes fails to respect cwd when it is run using check_call so
+ # we manually switch folders here instead.
+ project_root = get_project_root()
+ os.chdir(project_root)
+
+ if should_clobber:
+ node_modules_path = os.path.join(project_root, "node_modules")
+ print("Clobbering %s..." % node_modules_path)
+ if sys.platform.startswith("win") and have_winrm():
+ process = subprocess.Popen(["winrm", "-rf", node_modules_path])
+ process.wait()
+ else:
+ mozfileremove(node_modules_path)
+
+ npm_path, _ = find_npm_executable()
+ if not npm_path:
+ return 1
+
+ node_path, _ = find_node_executable()
+ if not node_path:
+ return 1
+
+ extra_parameters = ["--loglevel=error"]
+
+ if no_optional:
+ extra_parameters.append("--no-optional")
+
+ package_lock_json_path = os.path.join(get_project_root(), "package-lock.json")
+
+ if should_update:
+ cmd = [npm_path, "install"]
+ mozfileremove(package_lock_json_path)
+ else:
+ cmd = [npm_path, "ci"]
+
+ # On non-Windows, ensure npm is called via node, as node may not be in the
+ # path.
+ if platform.system() != "Windows":
+ cmd.insert(0, node_path)
+
+ cmd.extend(extra_parameters)
+
+ # Ensure that bare `node` and `npm` in scripts, including post-install scripts, finds the
+ # binary we're invoking with. Without this, it's easy for compiled extensions to get
+ # mismatched versions of the Node.js extension API.
+ path = os.environ.get("PATH", "").split(os.pathsep)
+ node_dir = os.path.dirname(node_path)
+ if node_dir not in path:
+ path = [node_dir] + path
+
+ print('Installing %s for mach using "%s"...' % (package_name, " ".join(cmd)))
+ result = call_process(
+ package_name, cmd, append_env={"PATH": os.pathsep.join(path)}
+ )
+
+ if not result:
+ return 1
+
+ bin_path = os.path.join(
+ get_project_root(), "node_modules", ".bin", package_name
+ )
+
+ print("\n%s installed successfully!" % package_name)
+ print("\nNOTE: Your local %s binary is at %s\n" % (package_name, bin_path))
+
+ finally:
+ set_project_root(orig_project_root)
+ os.chdir(orig_cwd)
+
+
+def call_process(name, cmd, cwd=None, append_env={}):
+ env = dict(os.environ)
+ env.update(append_env)
+
+ try:
+ with open(os.devnull, "w") as fnull:
+ subprocess.check_call(cmd, cwd=cwd, stdout=fnull, env=env)
+ except subprocess.CalledProcessError:
+ if cwd:
+ print("\nError installing %s in the %s folder, aborting." % (name, cwd))
+ else:
+ print("\nError installing %s, aborting." % name)
+
+ return False
+
+ return True
+
+
+def expected_eslint_modules():
+ # Read the expected version of ESLint and external modules
+ expected_modules_path = os.path.join(get_project_root(), "package.json")
+ with open(expected_modules_path, "r", encoding="utf-8") as f:
+ sections = json.load(f)
+ expected_modules = sections.get("dependencies", {})
+ expected_modules.update(sections.get("devDependencies", {}))
+
+ # Also read the in-tree ESLint plugin mozilla information, to ensure the
+ # dependencies are up to date.
+ # Bug 1766659: This is disabled for now due to sub-dependencies at the top
+ # level providing different module versions to those in eslint-plugin-mozilla.
+ # mozilla_json_path = os.path.join(
+ # get_eslint_module_path(), "eslint-plugin-mozilla", "package.json"
+ # )
+ # with open(mozilla_json_path, "r", encoding="utf-8") as f:
+ # expected_modules.update(json.load(f).get("dependencies", {}))
+
+ # Also read the in-tree ESLint plugin spidermonkey information, to ensure the
+ # dependencies are up to date.
+ mozilla_json_path = os.path.join(
+ get_eslint_module_path(), "eslint-plugin-spidermonkey-js", "package.json"
+ )
+ with open(mozilla_json_path, "r", encoding="utf-8") as f:
+ expected_modules.update(json.load(f).get("dependencies", {}))
+
+ return expected_modules
+
+
+def check_eslint_files(node_modules_path, name):
+ def check_file_diffs(dcmp):
+ # Diff files only looks at files that are different. Not for files
+ # that are only present on one side. This should be generally OK as
+ # new files will need to be added in the index.js for the package.
+ if dcmp.diff_files and dcmp.diff_files != ["package.json"]:
+ return True
+
+ result = False
+
+ # Again, we only look at common sub directories for the same reason
+ # as above.
+ for sub_dcmp in dcmp.subdirs.values():
+ result = result or check_file_diffs(sub_dcmp)
+
+ return result
+
+ dcmp = dircmp(
+ os.path.join(node_modules_path, name),
+ os.path.join(get_eslint_module_path(), name),
+ )
+
+ return check_file_diffs(dcmp)
+
+
+def eslint_module_needs_setup():
+ has_issues = False
+ needs_clobber = False
+ node_modules_path = os.path.join(get_project_root(), "node_modules")
+
+ for name, expected_data in expected_eslint_modules().items():
+ # expected_eslint_modules returns a string for the version number of
+ # dependencies for installation of eslint generally, and an object
+ # for our in-tree plugins (which contains the entire module info).
+ if "version" in expected_data:
+ version_range = expected_data["version"]
+ else:
+ version_range = expected_data
+
+ path = os.path.join(node_modules_path, name, "package.json")
+
+ if not os.path.exists(path):
+ print("%s v%s needs to be installed locally." % (name, version_range))
+ has_issues = True
+ continue
+ data = json.load(open(path, encoding="utf-8"))
+
+ if version_range.startswith("file:"):
+ # We don't need to check local file installations for versions, as
+ # these are symlinked, so we'll always pick up the latest.
+ continue
+
+ if name == "eslint" and Version("4.0.0") > Version(data["version"]):
+ print("ESLint is an old version, clobbering node_modules directory")
+ needs_clobber = True
+ has_issues = True
+ continue
+
+ # For @microsoft/eslint-plugin-sdl we are loading a static version as
+ # long that PR is not merged into the master branch. Bug 1786290
+ if (name == "@microsoft/eslint-plugin-sdl") and (
+ version_range
+ == "github:mozfreddyb/eslint-plugin-sdl#17b22cd527682108af7a1a4edacf69cb7a9b4a06"
+ ):
+ continue
+
+ if not version_in_range(data["version"], version_range):
+ print("%s v%s should be v%s." % (name, data["version"], version_range))
+ has_issues = True
+ continue
+
+ return has_issues, needs_clobber
+
+
+def version_in_range(version, version_range):
+ """
+ Check if a module version is inside a version range. Only supports explicit versions and
+ caret ranges for the moment, since that's all we've used so far.
+ """
+ if version == version_range:
+ return True
+
+ version_match = VERSION_RE.match(version)
+ if not version_match:
+ raise RuntimeError("mach eslint doesn't understand module version %s" % version)
+ version = Version(version)
+
+ # Caret ranges as specified by npm allow changes that do not modify the left-most non-zero
+ # digit in the [major, minor, patch] tuple. The code below assumes the major digit is
+ # non-zero.
+ range_match = CARET_VERSION_RANGE_RE.match(version_range)
+ if range_match:
+ range_version = range_match.group(1)
+ range_major = int(range_match.group(2))
+
+ range_min = Version(range_version)
+ range_max = Version("%d.0.0" % (range_major + 1))
+
+ return range_min <= version < range_max
+
+ return False
+
+
+def get_possible_node_paths_win():
+ """
+ Return possible nodejs paths on Windows.
+ """
+ if platform.system() != "Windows":
+ return []
+
+ return list(
+ {
+ "%s\\nodejs" % os.environ.get("SystemDrive"),
+ os.path.join(os.environ.get("ProgramFiles"), "nodejs"),
+ os.path.join(os.environ.get("PROGRAMW6432"), "nodejs"),
+ os.path.join(os.environ.get("PROGRAMFILES"), "nodejs"),
+ }
+ )
+
+
+def get_version(path):
+ try:
+ version_str = subprocess.check_output(
+ [path, "--version"], stderr=subprocess.STDOUT, universal_newlines=True
+ )
+ return version_str
+ except (subprocess.CalledProcessError, OSError):
+ return None
+
+
+def set_project_root(root=None):
+ """Sets the project root to the supplied path, or works out what the root
+ is based on looking for 'mach'.
+
+ Keyword arguments:
+ root - (optional) The path to set the root to.
+ """
+ global project_root
+
+ if root:
+ project_root = root
+ return
+
+ file_found = False
+ folder = os.getcwd()
+
+ while folder:
+ if os.path.exists(os.path.join(folder, "mach")):
+ file_found = True
+ break
+ else:
+ folder = os.path.dirname(folder)
+
+ if file_found:
+ project_root = os.path.abspath(folder)
+
+
+def get_project_root():
+ """Returns the absolute path to the root of the project, see set_project_root()
+ for how this is determined.
+ """
+ global project_root
+
+ if not project_root:
+ set_project_root()
+
+ return project_root
+
+
+def get_eslint_module_path():
+ return os.path.join(get_project_root(), "tools", "lint", "eslint")
+
+
+def check_node_executables_valid():
+ node_path, version = find_node_executable()
+ if not node_path:
+ print(NODE_NOT_FOUND_MESSAGE)
+ return False
+ if not version:
+ print(NODE_MACHING_VERSION_NOT_FOUND_MESSAGE % NODE_MIN_VERSION)
+ return False
+
+ npm_path, version = find_npm_executable()
+ if not npm_path:
+ print(NPM_NOT_FOUND_MESSAGE)
+ return False
+ if not version:
+ print(NPM_MACHING_VERSION_NOT_FOUND_MESSAGE % NPM_MIN_VERSION)
+ return False
+
+ return True
+
+
+def have_winrm():
+ # `winrm -h` should print 'winrm version ...' and exit 1
+ try:
+ p = subprocess.Popen(
+ ["winrm.exe", "-h"], stdout=subprocess.PIPE, stderr=subprocess.STDOUT
+ )
+ return p.wait() == 1 and p.stdout.read().startswith("winrm")
+ except Exception:
+ return False
diff --git a/tools/lint/eslint/update.sh b/tools/lint/eslint/update.sh
new file mode 100755
index 0000000000..e651170f5e
--- /dev/null
+++ b/tools/lint/eslint/update.sh
@@ -0,0 +1,71 @@
+#!/bin/sh
+# Script to regenerate the npm packages used for ESLint by the builders.
+# Requires
+
+# Force the scripts working directory to be projdir/tools/lint/eslint.
+DIR="$( cd "$( dirname "${BASH_SOURCE[0]}" )" && pwd )"
+cd $DIR
+
+echo "To complete this script you will need the following tokens from https://mozilla-releng.net/tokens"
+echo " - tooltool.upload.public"
+echo " - tooltool.download.public"
+echo ""
+read -p "Are these tokens visible at the above URL (y/n)?" choice
+case "$choice" in
+ y|Y )
+ echo ""
+ echo "1. Go to https://mozilla-releng.net"
+ echo "2. Log in using your Mozilla LDAP account."
+ echo "3. Click on \"Tokens.\""
+ echo "4. Issue a user token with the permissions tooltool.upload.public and tooltool.download.public."
+ echo ""
+ echo "When you click issue you will be presented with a long string. Paste the string into a temporary file called ~/.tooltool-token."
+ echo ""
+ read -rsp $'Press any key to continue...\n' -n 1
+ ;;
+ n|N )
+ echo ""
+ echo "You will need to contact somebody that has these permissions... people most likely to have these permissions are members of the releng, ateam, a sheriff, mratcliffe, or jryans"
+ exit 1
+ ;;
+ * )
+ echo ""
+ echo "Invalid input."
+ continue
+ ;;
+esac
+
+echo ""
+echo "Removing node_modules and package-lock.json..."
+# Move to the top-level directory.
+cd ../../../
+rm -rf node_modules/
+rm -rf tools/lint/eslint/eslint-plugin-mozilla/node_modules
+rm package-lock.json
+
+echo "Installing eslint and external plugins..."
+# ESLint and all _external_ plugins are listed in this directory's package.json,
+# so a regular `npm install` will install them at the specified versions.
+# The in-tree eslint-plugin-mozilla is kept out of this tooltool archive on
+# purpose so that it can be changed by any developer without requiring tooltool
+# access to make changes.
+./mach npm install
+
+echo "Creating eslint.tar.gz..."
+tar cvz --exclude=eslint-plugin-mozilla --exclude=eslint-plugin-spidermonkey-js -f eslint.tar.gz node_modules
+
+echo "Adding eslint.tar.gz to tooltool..."
+rm tools/lint/eslint/manifest.tt
+./python/mozbuild/mozbuild/action/tooltool.py add --visibility public --unpack eslint.tar.gz
+
+echo "Uploading eslint.tar.gz to tooltool..."
+./python/mozbuild/mozbuild/action/tooltool.py upload --authentication-file=~/.tooltool-token --message "node_modules folder update for tools/lint/eslint"
+
+echo "Cleaning up..."
+mv manifest.tt tools/lint/eslint/manifest.tt
+rm eslint.tar.gz
+
+cd $DIR
+
+echo ""
+echo "Update complete, please commit and check in your changes."
diff --git a/tools/lint/file-perm.yml b/tools/lint/file-perm.yml
new file mode 100644
index 0000000000..805f6439ca
--- /dev/null
+++ b/tools/lint/file-perm.yml
@@ -0,0 +1,53 @@
+---
+file-perm:
+ description: File permission check
+ include:
+ - .
+ extensions:
+ - .build
+ - .c
+ - .cc
+ - .cpp
+ - .flac
+ - .h
+ - .html
+ - .idl
+ - .js
+ - .jsm
+ - .jsx
+ - .m
+ - .m4s
+ - .md
+ - .mjs
+ - .mm
+ - .mozbuild
+ - .mp4
+ - .png
+ - .rs
+ - .rst
+ - .svg
+ - .ttf
+ - .wasm
+ - .webidl
+ - .xhtml
+ - .xml
+ - .xul
+ - .yml
+ support-files:
+ - 'tools/lint/file-perm/**'
+ type: external
+ payload: file-perm:lint
+
+maybe-shebang-file-perm:
+ description: "File permission check for files that might have `#!` header."
+ include:
+ - .
+ allow-shebang: true
+ extensions:
+ - .js
+ - .py
+ - .sh
+ support-files:
+ - 'tools/lint/file-perm/**'
+ type: external
+ payload: file-perm:lint
diff --git a/tools/lint/file-perm/__init__.py b/tools/lint/file-perm/__init__.py
new file mode 100644
index 0000000000..c5f31c008c
--- /dev/null
+++ b/tools/lint/file-perm/__init__.py
@@ -0,0 +1,45 @@
+# 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 platform
+
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+
+def lint(paths, config, fix=None, **lintargs):
+ results = []
+ fixed = 0
+
+ if platform.system() == "Windows":
+ # Windows doesn't have permissions in files
+ # Exit now
+ return {"results": results, "fixed": fixed}
+
+ files = list(expand_exclusions(paths, config, lintargs["root"]))
+ for f in files:
+ if os.access(f, os.X_OK):
+ if config.get("allow-shebang"):
+ with open(f, "r+") as content:
+ # Some source files have +x permissions
+ line = content.readline()
+ if line.startswith("#!"):
+ # Check if the file doesn't start with a shebang
+ # if it does, not a warning
+ continue
+
+ if fix:
+ # We want to fix it, do it and leave
+ os.chmod(f, 0o644)
+ fixed += 1
+ continue
+
+ res = {
+ "path": f,
+ "message": "Execution permissions on a source file",
+ "level": "error",
+ }
+ results.append(result.from_config(config, **res))
+ return {"results": results, "fixed": fixed}
diff --git a/tools/lint/file-whitespace.yml b/tools/lint/file-whitespace.yml
new file mode 100644
index 0000000000..e598a28414
--- /dev/null
+++ b/tools/lint/file-whitespace.yml
@@ -0,0 +1,163 @@
+---
+file-whitespace:
+ description: File content sanity check
+ include:
+ - .
+ - tools/lint/python/flake8_requirements.txt
+ - tools/lint/python/pylint_requirements.txt
+ - tools/lint/python/black_requirements.txt
+ - tools/lint/rst/requirements.txt
+ - tools/lint/tox/tox_requirements.txt
+ - tools/lint/spell/codespell_requirements.txt
+ - tools/tryselect/selectors/chooser/requirements.txt
+ exclude:
+ - accessible/tests/crashtests
+ - accessible/tests/mochitest
+ - browser/locales/en-US/chrome/browser/uiDensity.properties
+ - build/pgo/blueprint
+ - build/pgo/js-input
+ - devtools/client/debugger/test
+ - devtools/client/inspector/markup/test
+ - devtools/client/inspector/rules/test
+ - devtools/client/inspector/test
+ # Excluded because tests were failing unexpectedly
+ - devtools/client/styleeditor/test/sync_with_csp.css
+ - devtools/client/webconsole/test/browser/test-message-categories-css-parser.css
+ - devtools/shared/webconsole/test/chrome/test_object_actor_native_getters.html
+ - docshell/base/crashtests
+ - docshell/test
+ - dom/base/crashtests
+ - dom/base/test
+ - dom/canvas/crashtests
+ - dom/canvas/test
+ - dom/events/crashtests
+ - dom/events/test
+ - dom/file/tests/file_mozfiledataurl_inner.html
+ - dom/html/crashtests
+ - dom/html/reftests
+ - dom/html/test
+ - dom/jsurl/crashtests/344996-1.xhtml
+ - dom/jsurl/test
+ - dom/media/mediasource/test/crashtests/926665.html
+ - dom/media/test
+ - dom/media/tests
+ - dom/media/webaudio/test
+ - dom/media/webrtc/transport/nricectx.cpp
+ - dom/media/webspeech/synth/test
+ - dom/plugins/test
+ - dom/smil/crashtests
+ - dom/smil/test
+ - dom/security/test
+ - dom/svg/crashtests
+ - dom/svg/test
+ - dom/webauthn/winwebauthn/webauthn.h
+ - dom/tests/mochitest
+ - dom/xml/crashtests
+ - dom/xml/test
+ - dom/xslt/crashtests
+ - dom/xslt/tests
+ - dom/xul/crashtests
+ - dom/xul/test
+ - editor/composer/test
+ - editor/composer/crashtests/removing-editable-xslt.html
+ - editor/libeditor/tests
+ - editor/libeditor/crashtests
+ - editor/reftests
+ - extensions/universalchardet
+ - gfx/tests/crashtests
+ - gfx/vr/nsFxrCommandLineHandler.cpp
+ - image/test/crashtests
+ - image/test/mochitest
+ - image/test/reftest
+ - intl/lwbrk/crashtests
+ - intl/uconv/crashtests
+ - intl/uconv/tests
+ - intl/strres/tests/unit/397093.properties
+ - intl/strres/tests/unit/strres.properties
+ - js/xpconnect/crashtests
+ - js/xpconnect/tests
+ - js/src/frontend/BytecodeEmitter.cpp
+ - js/src/frontend/SharedContext.h
+ - layout/base/crashtests
+ - layout/base/tests
+ - layout/forms/crashtests
+ - layout/forms/test
+ - layout/generic/crashtests
+ - layout/generic/test
+ - layout/inspector/tests
+ - layout/mathml/tests
+ - layout/painting/crashtests/1405881-1.html
+ - layout/painting/crashtests/1407470-1.html
+ - layout/reftests
+ - layout/style/crashtests
+ - layout/style/test
+ - layout/svg/crashtests
+ - layout/tables/test/test_bug337124.html
+ - layout/tables/crashtests
+ - layout/xul/crashtests
+ - layout/xul/reftest
+ - layout/xul/test
+ - layout/xul/tree
+ - modules/libjar/zipwriter/test/unit/data/test_bug399727.html
+ - netwerk/test/crashtests
+ - netwerk/test/mochitests/test1.css
+ - netwerk/test/mochitests/test2.css
+ - parser/htmlparser/tests
+ - parser/html/java/named-character-references.html
+ - testing/perfdocs/generated/
+ - testing/talos/talos/pageloader/chrome/pageloader.xhtml
+ - testing/talos/talos/tests
+ - testing/web-platform/mozilla/tests/editor
+ - testing/web-platform/mozilla/tests/focus
+ - testing/web-platform/tests
+ - testing/web-platform/tests/conformance-checkers
+ - testing/web-platform/tests/content-security-policy
+ - testing/web-platform/tests/css/tools/w3ctestlib/templates/indices.css
+ - testing/web-platform/tests/html
+ - testing/web-platform/tests/tools/lint/tests/dummy/broken.html
+ - testing/web-platform/tests/tools/lint/tests/dummy/broken_ignored.html
+ - toolkit/content/tests/chrome
+ - tools/jprof/README.html
+ - tools/lint/eslint
+ - tools/lint/test/test_clang_format.py
+ - view/crashtests
+ - widget/cocoa/crashtests
+ - widget/nsFilePickerProxy.cpp
+ - widget/tests
+ - widget/windows/tests/TestUrisToValidate.h
+ - xpcom/reflect/xptcall/porting.html
+ - xpcom/reflect/xptcall/status.html
+ - xpcom/tests/test.properties
+ - xpcom/tests/unit/data/bug121341.properties
+ # Excluded below files because tests were failing unexpectedly
+ - dom/bindings/test/test_barewordGetsWindow.html
+ - devtools/client/styleeditor/test/sourcemap-css/sourcemaps.css
+ - devtools/client/styleeditor/test/sourcemap-css/contained.css
+ - devtools/client/styleeditor/test/sourcemap-css/test-stylus.css
+ - dom/bindings/test/file_barewordGetsWindow_frame1.html
+ - dom/bindings/test/file_barewordGetsWindow_frame2.html
+ - devtools/perfdocs/index.rst
+ - python/mozperftest/perfdocs/running.rst
+ - python/mozperftest/perfdocs/vision.rst
+ - python/mozperftest/perfdocs/writing.rst
+ extensions:
+ - .c
+ - .cc
+ - .cpp
+ - .css
+ - .dtd
+ - .idl
+ - .ftl
+ - .h
+ - .html
+ - .md
+ - .properties
+ - .py
+ - .rs
+ - .rst
+ - .webidl
+ - .xhtml
+ support-files:
+ - 'tools/lint/file-whitespace/**'
+ type: external
+ payload: file-whitespace:lint
diff --git a/tools/lint/file-whitespace/__init__.py b/tools/lint/file-whitespace/__init__.py
new file mode 100644
index 0000000000..ab7a6944b7
--- /dev/null
+++ b/tools/lint/file-whitespace/__init__.py
@@ -0,0 +1,124 @@
+# 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/.
+
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+results = []
+
+
+def lint(paths, config, fix=None, **lintargs):
+ files = list(expand_exclusions(paths, config, lintargs["root"]))
+ log = lintargs["log"]
+ fixed = 0
+
+ for f in files:
+ with open(f, "rb") as open_file:
+ hasFix = False
+ content_to_write = []
+
+ try:
+ lines = open_file.readlines()
+ # Check for Empty spaces or newline character at end of file
+ if lines[:].__len__() != 0 and lines[-1:][0].strip().__len__() == 0:
+ # return file pointer to first
+ open_file.seek(0)
+ if fix:
+ fixed += 1
+ # fix Empty lines at end of file
+ for i, line in reversed(list(enumerate(open_file))):
+ # determine if line is empty
+ if line.strip() != b"":
+ with open(f, "wb") as write_file:
+ # determine if file's last line have \n, if not then add a \n
+ if not lines[i].endswith(b"\n"):
+ lines[i] = lines[i] + b"\n"
+ # write content to file
+ for e in lines[: i + 1]:
+ write_file.write(e)
+ # end the loop
+ break
+ else:
+ res = {
+ "path": f,
+ "message": "Empty Lines at end of file",
+ "level": "error",
+ "lineno": open_file.readlines()[:].__len__(),
+ }
+ results.append(result.from_config(config, **res))
+ except Exception as ex:
+ log.debug("Error: " + str(ex) + ", in file: " + f)
+
+ # return file pointer to first
+ open_file.seek(0)
+
+ lines = open_file.readlines()
+ # Detect missing newline at the end of the file
+ if lines[:].__len__() != 0 and not lines[-1].endswith(b"\n"):
+ if fix:
+ fixed += 1
+ with open(f, "wb") as write_file:
+ # add a newline character at end of file
+ lines[-1] = lines[-1] + b"\n"
+ # write content to file
+ for e in lines:
+ write_file.write(e)
+ else:
+ res = {
+ "path": f,
+ "message": "File does not end with newline character",
+ "level": "error",
+ "lineno": lines.__len__(),
+ }
+ results.append(result.from_config(config, **res))
+
+ # return file pointer to first
+ open_file.seek(0)
+
+ for i, line in enumerate(open_file):
+ if line.endswith(b" \n"):
+ # We found a trailing whitespace
+ if fix:
+ # We want to fix it, strip the trailing spaces
+ content_to_write.append(line.rstrip() + b"\n")
+ fixed += 1
+ hasFix = True
+ else:
+ res = {
+ "path": f,
+ "message": "Trailing whitespace",
+ "level": "error",
+ "lineno": i + 1,
+ }
+ results.append(result.from_config(config, **res))
+ else:
+ if fix:
+ content_to_write.append(line)
+ if hasFix:
+ # Only update the file when we found a change to make
+ with open(f, "wb") as open_file_to_write:
+ open_file_to_write.write(b"".join(content_to_write))
+
+ # We are still using the same fp, let's return to the first
+ # line
+ open_file.seek(0)
+ # Open it as once as we just need to know if there is
+ # at least one \r\n
+ content = open_file.read()
+
+ if b"\r\n" in content:
+ if fix:
+ fixed += 1
+ content = content.replace(b"\r\n", b"\n")
+ with open(f, "wb") as open_file_to_write:
+ open_file_to_write.write(content)
+ else:
+ res = {
+ "path": f,
+ "message": "Windows line return",
+ "level": "error",
+ }
+ results.append(result.from_config(config, **res))
+
+ return {"results": results, "fixed": fixed}
diff --git a/tools/lint/flake8.yml b/tools/lint/flake8.yml
new file mode 100644
index 0000000000..11d3193271
--- /dev/null
+++ b/tools/lint/flake8.yml
@@ -0,0 +1,15 @@
+---
+flake8:
+ description: Python linter
+ # Excludes should be added to topsrcdir/.flake8.
+ exclude: []
+ # The configure option is used by the build system
+ extensions: ['configure', 'py']
+ support-files:
+ - '**/.flake8'
+ - 'tools/lint/python/flake8*'
+ # Rules that should result in warnings rather than errors.
+ warning-rules: []
+ type: external
+ payload: python.flake8:lint
+ setup: python.flake8:setup
diff --git a/tools/lint/fluent-lint.yml b/tools/lint/fluent-lint.yml
new file mode 100644
index 0000000000..b3f4023dc2
--- /dev/null
+++ b/tools/lint/fluent-lint.yml
@@ -0,0 +1,10 @@
+---
+fluent-lint:
+ description: Linter for Fluent files
+ exclude:
+ - dom/l10n/tests/mochitest/document_l10n/non-system-principal/localization/test.ftl
+ extensions: ['ftl']
+ support-files:
+ - 'tools/lint/fluent-lint**'
+ type: external
+ payload: fluent-lint:lint
diff --git a/tools/lint/fluent-lint/__init__.py b/tools/lint/fluent-lint/__init__.py
new file mode 100644
index 0000000000..adf5aa09be
--- /dev/null
+++ b/tools/lint/fluent-lint/__init__.py
@@ -0,0 +1,381 @@
+# 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 bisect
+import re
+from html.parser import HTMLParser
+
+import mozpack.path as mozpath
+import yaml
+from fluent.syntax import parse, visitor
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+
+class TextElementHTMLParser(HTMLParser):
+ """HTML Parser for TextElement.
+
+ TextElements may contain embedded html tags, which can include
+ quotes in attributes. We only want to check the actual text.
+ """
+
+ def __init__(self):
+ super().__init__()
+ self.extracted_text = []
+
+ def handle_data(self, data):
+ self.extracted_text.append(data)
+
+
+class Linter(visitor.Visitor):
+ """Fluent linter implementation.
+
+ This subclasses the Fluent AST visitor. Methods are called corresponding
+ to each type of node in the Fluent AST. It is possible to control
+ whether a node is recursed into by calling the generic_visit method on
+ the superclass.
+
+ See the documentation here:
+ https://www.projectfluent.org/python-fluent/fluent.syntax/stable/usage.html
+ """
+
+ def __init__(self, path, config, exclusions, contents, offsets_and_lines):
+ super().__init__()
+ self.path = path
+ self.config = config
+ self.exclusions = exclusions
+ self.contents = contents
+ self.offsets_and_lines = offsets_and_lines
+
+ self.results = []
+ self.identifier_re = re.compile(r"[a-z0-9-]+")
+ self.apostrophe_re = re.compile(r"\w'")
+ self.incorrect_apostrophe_re = re.compile(r"\w\u2018\w")
+ self.single_quote_re = re.compile(r"'(.+)'")
+ self.double_quote_re = re.compile(r"\".+\"")
+ self.ellipsis_re = re.compile(r"\.\.\.")
+
+ self.brand_names = ["Firefox", "Mozilla", "Thunderbird"]
+ self.minimum_id_length = 9
+
+ self.state = {
+ # The resource comment should be at the top of the page after the license.
+ "node_can_be_resource_comment": True,
+ # Group comments must be followed by a message. Two group comments are not
+ # allowed in a row.
+ "can_have_group_comment": True,
+ }
+
+ # Set this to true to debug print the root node's json. This is useful for
+ # writing new lint rules, or debugging existing ones.
+ self.debug_print_json = False
+
+ def generic_visit(self, node):
+ node_name = type(node).__name__
+ self.state["node_can_be_resource_comment"] = self.state[
+ "node_can_be_resource_comment"
+ ] and (
+ # This is the root node.
+ node_name == "Resource"
+ # Empty space is allowed.
+ or node_name == "Span"
+ # Comments are allowed
+ or node_name == "Comment"
+ )
+
+ if self.debug_print_json:
+ import json
+
+ print(json.dumps(node.to_json(), indent=2))
+ # Only debug print the root node.
+ self.debug_print_json = False
+
+ super(Linter, self).generic_visit(node)
+
+ def visit_Attribute(self, node):
+ # Only visit values for Attribute nodes, the identifier comes from dom.
+ super().generic_visit(node.value)
+
+ def visit_FunctionReference(self, node):
+ # We don't recurse into function references, the identifiers there are
+ # allowed to be free form.
+ pass
+
+ def visit_Message(self, node):
+ # There must be at least one message or term between group comments.
+ self.state["can_have_group_comment"] = True
+ self.last_message_id = node.id.name
+
+ super().generic_visit(node)
+
+ def visit_Term(self, node):
+ # There must be at least one message or term between group comments.
+ self.state["can_have_group_comment"] = True
+ self.last_message_id = None
+
+ super().generic_visit(node)
+
+ def visit_MessageReference(self, node):
+ # We don't recurse into message references, the identifiers are either
+ # checked elsewhere or are attributes and come from DOM.
+ pass
+
+ def visit_Identifier(self, node):
+ if (
+ self.path not in self.exclusions["ID01"]["files"]
+ and node.name not in self.exclusions["ID01"]["messages"]
+ and not self.identifier_re.fullmatch(node.name)
+ ):
+ self.add_error(
+ node,
+ "ID01",
+ "Identifiers may only contain lowercase characters and -",
+ )
+ if (
+ len(node.name) < self.minimum_id_length
+ and self.path not in self.exclusions["ID02"]["files"]
+ and node.name not in self.exclusions["ID02"]["messages"]
+ ):
+ self.add_error(
+ node,
+ "ID02",
+ f"Identifiers must be at least {self.minimum_id_length} characters long",
+ )
+
+ def visit_TextElement(self, node):
+ parser = TextElementHTMLParser()
+ parser.feed(node.value)
+ for text in parser.extracted_text:
+ # To check for apostrophes, first remove pairs of straight quotes
+ # used as delimiters.
+ cleaned_str = re.sub(self.single_quote_re, "\1", node.value)
+ if self.apostrophe_re.search(cleaned_str):
+ self.add_error(
+ node,
+ "TE01",
+ "Strings with apostrophes should use foo\u2019s instead of foo's.",
+ )
+ if self.incorrect_apostrophe_re.search(text):
+ self.add_error(
+ node,
+ "TE02",
+ "Strings with apostrophes should use foo\u2019s instead of foo\u2018s.",
+ )
+ if self.single_quote_re.search(text):
+ self.add_error(
+ node,
+ "TE03",
+ "Single-quoted strings should use Unicode \u2018foo\u2019 instead of 'foo'.",
+ )
+ if self.double_quote_re.search(text):
+ self.add_error(
+ node,
+ "TE04",
+ 'Double-quoted strings should use Unicode \u201cfoo\u201d instead of "foo".',
+ )
+ if self.ellipsis_re.search(text):
+ self.add_error(
+ node,
+ "TE05",
+ "Strings with an ellipsis should use the Unicode \u2026 character"
+ " instead of three periods",
+ )
+
+ # If part of a message, check for brand names
+ if (
+ self.last_message_id is not None
+ and self.path not in self.exclusions["CO01"]["files"]
+ and self.last_message_id not in self.exclusions["CO01"]["messages"]
+ ):
+ found_brands = []
+ for brand in self.brand_names:
+ if brand in text:
+ found_brands.append(brand)
+ if found_brands:
+ self.add_error(
+ node,
+ "CO01",
+ "Strings should use the corresponding terms instead of"
+ f" hard-coded brand names ({', '.join(found_brands)})",
+ )
+
+ def visit_ResourceComment(self, node):
+ # This node is a comment with: "###"
+ if not self.state["node_can_be_resource_comment"]:
+ self.add_error(
+ node,
+ "RC01",
+ "Resource comments (###) should be placed at the top of the file, just "
+ "after the license header. There should only be one resource comment "
+ "per file.",
+ )
+ return
+
+ lines_after = get_newlines_count_after(node.span, self.contents)
+ lines_before = get_newlines_count_before(node.span, self.contents)
+
+ if node.span.end == len(self.contents) - 1:
+ # This file only contains a resource comment.
+ return
+
+ if lines_after != 2:
+ self.add_error(
+ node,
+ "RC02",
+ "Resource comments (###) should be followed by one empty line.",
+ )
+ return
+
+ if lines_before != 2:
+ self.add_error(
+ node,
+ "RC03",
+ "Resource comments (###) should have one empty line above them.",
+ )
+ return
+
+ def visit_SelectExpression(self, node):
+ # We only want to visit the variant values, the identifiers in selectors
+ # and keys are allowed to be free form.
+ for variant in node.variants:
+ super().generic_visit(variant.value)
+
+ def visit_GroupComment(self, node):
+ # This node is a comment with: "##"
+
+ if not self.state["can_have_group_comment"]:
+ self.add_error(
+ node,
+ "GC04",
+ "Group comments (##) must be followed by at least one message "
+ "or term. Make sure that a single group comment with multiple "
+ "pararaphs is not separated by whitespace, as it will be "
+ "interpreted as two different comments.",
+ )
+ return
+
+ self.state["can_have_group_comment"] = False
+
+ lines_after = get_newlines_count_after(node.span, self.contents)
+ lines_before = get_newlines_count_before(node.span, self.contents)
+
+ if node.span.end == len(self.contents) - 1:
+ # The group comment is the last thing in the file.
+
+ if node.content == "":
+ # Empty comments are allowed at the end of the file.
+ return
+
+ self.add_error(
+ node,
+ "GC01",
+ "Group comments (##) should not be at the end of the file, they should "
+ "always be above a message. Only an empty group comment is allowed at "
+ "the end of a file.",
+ )
+ return
+
+ if lines_after != 2:
+ self.add_error(
+ node,
+ "GC02",
+ "Group comments (##) should be followed by one empty line.",
+ )
+ return
+
+ if lines_before != 2:
+ self.add_error(
+ node,
+ "GC03",
+ "Group comments (##) should have an empty line before them.",
+ )
+ return
+
+ def visit_VariableReference(self, node):
+ # We don't recurse into variable references, the identifiers there are
+ # allowed to be free form.
+ pass
+
+ def add_error(self, node, rule, msg):
+ (col, line) = self.span_to_line_and_col(node.span)
+ res = {
+ "path": self.path,
+ "lineno": line,
+ "column": col,
+ "rule": rule,
+ "message": msg,
+ }
+ self.results.append(result.from_config(self.config, **res))
+
+ def span_to_line_and_col(self, span):
+ i = bisect.bisect_left(self.offsets_and_lines, (span.start, 0))
+ if i > 0:
+ col = span.start - self.offsets_and_lines[i - 1][0]
+ else:
+ col = 1 + span.start
+ return (col, self.offsets_and_lines[i][1])
+
+
+def get_offsets_and_lines(contents):
+ """Return a list consisting of tuples of (offset, line).
+
+ The Fluent AST contains spans of start and end offsets in the file.
+ This function returns a list of offsets and line numbers so that errors
+ can be reported using line and column.
+ """
+ line = 1
+ result = []
+ for m in re.finditer(r"\n", contents):
+ result.append((m.start(), line))
+ line += 1
+ return result
+
+
+def get_newlines_count_after(span, contents):
+ # Determine the number of newlines.
+ count = 0
+ for i in range(span.end, len(contents)):
+ assert contents[i] != "\r", "This linter does not handle \\r characters."
+ if contents[i] != "\n":
+ break
+ count += 1
+
+ return count
+
+
+def get_newlines_count_before(span, contents):
+ # Determine the range of newline characters.
+ count = 0
+ for i in range(span.start - 1, 0, -1):
+ assert contents[i] != "\r", "This linter does not handle \\r characters."
+ if contents[i] != "\n":
+ break
+ count += 1
+
+ return count
+
+
+def get_exclusions(root):
+ with open(
+ mozpath.join(root, "tools", "lint", "fluent-lint", "exclusions.yml")
+ ) as f:
+ exclusions = list(yaml.safe_load_all(f))[0]
+ for error_type in exclusions:
+ exclusions[error_type]["files"] = set(
+ [mozpath.join(root, x) for x in exclusions[error_type]["files"]]
+ )
+ return exclusions
+
+
+def lint(paths, config, fix=None, **lintargs):
+ files = list(expand_exclusions(paths, config, lintargs["root"]))
+ exclusions = get_exclusions(lintargs["root"])
+ results = []
+ for path in files:
+ contents = open(path, "r", encoding="utf-8").read()
+ linter = Linter(
+ path, config, exclusions, contents, get_offsets_and_lines(contents)
+ )
+ linter.visit(parse(contents))
+ results.extend(linter.results)
+ return results
diff --git a/tools/lint/fluent-lint/exclusions.yml b/tools/lint/fluent-lint/exclusions.yml
new file mode 100644
index 0000000000..c24b918e4b
--- /dev/null
+++ b/tools/lint/fluent-lint/exclusions.yml
@@ -0,0 +1,192 @@
+# 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/.
+
+# Warning: Only exclusions for identifiers (ID01) are currently allowed.
+---
+# Only add exceptions to this file if the ID is generated programmatically and
+# can't easily be changed to follow the naming convention.
+# Only lowercase letters and hyphens should be used in Fluent IDs.
+ID01:
+ messages:
+ - trademarkInfo
+ - crashed-include-URL-2
+ - blocklist-item-moz-std-listName
+ - blocklist-item-moz-full-listName
+ - shortcuts-browserAction2
+ - shortcuts-pageAction
+ - shortcuts-sidebarAction
+ - about-networking-originAttributesSuffix
+ - size-KB
+ - size-MB
+ - size-GB
+ - state-dd-Disabled
+ - state-dd-Disabled-block-list-state
+ - memory-unit-B
+ - memory-unit-KB
+ - memory-unit-MB
+ - memory-unit-GB
+ - memory-unit-TB
+ - memory-unit-PB
+ - memory-unit-EB
+ - enableSafeBrowsing-label
+ - about-telemetry-show-in-Firefox-json-viewer
+ - url-classifier-search-listType
+ # aboutDialog.ftl: Do not add new exceptions for this file,
+ # new strings should follow the naming convention.
+ - aboutDialog-title
+ - releaseNotes-link
+ - update-checkForUpdatesButton
+ - update-updateButton
+ - update-checkingForUpdates
+ - update-adminDisabled
+ - update-noUpdatesFound
+ - update-otherInstanceHandlingUpdates
+ - warningDesc-version
+ - bottomLinks-license
+ - bottomLinks-rights
+ - bottomLinks-privacy
+ - aboutDialog-version
+ - aboutDialog-version-nightly
+ # certError.ftl: These IDs are generated programmatically
+ # from certificate error codes.
+ - connectionFailure-title
+ - deniedPortAccess-title
+ - dnsNotFound-title
+ - fileNotFound-title
+ - fileAccessDenied-title
+ - captivePortal-title
+ - malformedURI-title
+ - netInterrupt-title
+ - notCached-title
+ - netOffline-title
+ - contentEncodingError-title
+ - unsafeContentType-title
+ - netReset-title
+ - netTimeout-title
+ - unknownProtocolFound-title
+ - proxyConnectFailure-title
+ - proxyResolveFailure-title
+ - redirectLoop-title
+ - unknownSocketType-title
+ - nssFailure2-title
+ - corruptedContentError-title
+ - sslv3Used-title
+ - inadequateSecurityError-title
+ - blockedByPolicy-title
+ - clockSkewError-title
+ - networkProtocolError-title
+ - nssBadCert-title
+ - nssBadCert-sts-title
+ files:
+ # policies-descriptions.ftl: These IDs are generated programmatically
+ # from policy names.
+ - browser/locales/en-US/browser/policies/policies-descriptions.ftl
+ID02:
+ messages:
+ # browser/components/ion/content/ion.ftl
+ - ion
+ # browser/locales/en-US/browser/aboutDialog.ftl
+ - helpus
+ # browser/locales/en-US/browser/aboutLogins.ftl
+ - menu
+ # browser/locales/en-US/browser/pageInfo.ftl
+ - copy
+ - perm-tab
+ # browser/locales/en-US/browser/tabContextMenu.ftl
+ - pin-tab
+ # browser/locales/en-US/browser/touchbar/touchbar.ftl
+ - back
+ - forward
+ - reload
+ - home
+ - find
+ - new-tab
+ - share
+ # toolkit/locales/en-US/toolkit/about/aboutPerformance.ftl
+ - type-tab
+ - size-KB
+ - size-MB
+ - size-GB
+ - item
+ # toolkit/locales/en-US/toolkit/about/aboutPlugins.ftl
+ - file-dd
+ - path-dd
+ # toolkit/locales/en-US/toolkit/about/aboutServiceWorkers.ftl
+ - scope
+ - waiting
+ # toolkit/locales/en-US/toolkit/about/aboutSupport.ftl
+ # yaml interprets yes and no as booleans if quotes are not present.
+ - "yes"
+ - "no"
+ - unknown
+ - found
+ - missing
+ - gpu-ram
+ - apz-none
+ # toolkit/locales/en-US/toolkit/printing/printDialogs.ftl
+ - portrait
+ - scale
+ - print-bg
+ - hf-blank
+ - hf-title
+ - hf-url
+ - hf-page
+ files: []
+# Hard-coded brand names like Firefox or Mozilla should be used only in
+# specific cases, in all other cases the corresponding terms should be used.
+# Check with the localization team for advice.
+CO01:
+ messages:
+ # browser/branding/official/locales/en-US/brand.ftl
+ # browser/locales/en-US/browser/branding/brandings.ftl
+ - trademarkInfo
+ # toolkit/locales/en-US/toolkit/neterror/certError.ftl
+ - cert-error-mitm-mozilla
+ - cert-error-mitm-connection
+ # browser/locales/en-US/browser/appExtensionFields.ftl
+ - extension-firefox-alpenglow-name
+ # browser/locales/en-US/browser/browser.ftl
+ - identity-custom-root
+ - identity-description-custom-root
+ # browser/locales/en-US/browser/migration.ftl
+ - import-from-firefox
+ # browser/locales/en-US/browser/newtab/onboarding.ftl
+ - mr1-onboarding-welcome-image-caption
+ - mr2022-onboarding-gratitude-subtitle
+ # browser/locales/en-US/browser/policies/policies-descriptions.ftl
+ - policy-DisableFirefoxScreenshots
+ # browser/locales/en-US/browser/preferences/preferences.ftl
+ - sync-engine-addons
+ - sync-mobile-promo
+ # browser/locales/en-US/browser/protectionsPanel.ftl
+ - protections-panel-content-blocking-breakage-report-view-description
+ # devtools/client/locales/en-US/aboutdebugging.ftl
+ - about-debugging-setup-usb-step-enable-debug-firefox2
+ - about-debugging-browser-version-too-old-fennec
+ - about-debugging-browser-version-too-recent
+ # devtools/client/locales/en-US/application.ftl
+ - manifest-loaded-devtools-error
+ # toolkit/locales/en-US/toolkit/about/aboutAddons.ftl
+ - addon-badge-line3
+ - recommended-theme-1
+ # toolkit/locales/en-US/toolkit/about/aboutGlean.ftl
+ - about-glean-description
+ # toolkit/locales/en-US/toolkit/about/aboutPlugins.ftl
+ - plugins-openh264-description
+ # toolkit/locales/en-US/toolkit/about/aboutRights.ftl
+ - rights-intro-point-1
+ - rights-intro-point-2
+ # toolkit/locales/en-US/toolkit/about/aboutSupport.ftl
+ - app-basics-key-mozilla
+ # toolkit/locales/en-US/toolkit/about/aboutTelemetry.ftl
+ - about-telemetry-firefox-data-doc
+ - about-telemetry-origins-explanation
+ - about-telemetry-telemetry-client-doc
+ - about-telemetry-telemetry-dashboard
+ # toolkit/locales/en-US/toolkit/global/processTypes.ftl
+ - process-type-privilegedmozilla
+ files:
+ - browser/components/ion/content/ion.ftl
+ - browser/locales/en-US/browser/profile/default-bookmarks.ftl
+ - toolkit/locales/en-US/toolkit/about/aboutMozilla.ftl
diff --git a/tools/lint/hooks.py b/tools/lint/hooks.py
new file mode 100755
index 0000000000..fead62ec7e
--- /dev/null
+++ b/tools/lint/hooks.py
@@ -0,0 +1,63 @@
+#!/usr/bin/env python
+# 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 signal
+import subprocess
+import sys
+from distutils.spawn import find_executable
+
+import six
+
+here = os.path.dirname(os.path.realpath(__file__))
+topsrcdir = os.path.join(here, os.pardir, os.pardir)
+
+
+def run_process(cmd):
+ proc = subprocess.Popen(cmd)
+
+ # ignore SIGINT, the mozlint subprocess should exit quickly and gracefully
+ orig_handler = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ proc.wait()
+ signal.signal(signal.SIGINT, orig_handler)
+ return proc.returncode
+
+
+def run_mozlint(hooktype, args):
+ if isinstance(hooktype, six.binary_type):
+ hooktype = hooktype.decode("UTF-8", "replace")
+
+ python = find_executable("python3")
+ if not python:
+ print("error: Python 3 not detected on your system! Please install it.")
+ sys.exit(1)
+
+ cmd = [python, os.path.join(topsrcdir, "mach"), "lint"]
+
+ if "commit" in hooktype:
+ # don't prevent commits, just display the lint results
+ run_process(cmd + ["--workdir=staged"])
+ return False
+ elif "push" in hooktype:
+ return run_process(cmd + ["--outgoing"] + args)
+
+ print("warning: '{}' is not a valid mozlint hooktype".format(hooktype))
+ return False
+
+
+def hg(ui, repo, **kwargs):
+ hooktype = kwargs["hooktype"]
+ return run_mozlint(hooktype, kwargs.get("pats", []))
+
+
+def git():
+ hooktype = os.path.basename(__file__)
+ if hooktype == "hooks.py":
+ hooktype = "pre-push"
+ return run_mozlint(hooktype, [])
+
+
+if __name__ == "__main__":
+ sys.exit(git())
diff --git a/tools/lint/hooks_clang_format.py b/tools/lint/hooks_clang_format.py
new file mode 100755
index 0000000000..9adb81b7f0
--- /dev/null
+++ b/tools/lint/hooks_clang_format.py
@@ -0,0 +1,99 @@
+#!/usr/bin/env python3
+# 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 subprocess
+import sys
+from subprocess import CalledProcessError, check_output
+
+here = os.path.dirname(os.path.realpath(__file__))
+topsrcdir = os.path.join(here, os.pardir, os.pardir)
+
+EXTRA_PATHS = (
+ "python/mach",
+ "python/mozbuild",
+ "python/mozversioncontrol",
+ "testing/mozbase/mozfile",
+ "third_party/python/jsmin",
+ "third_party/python/six",
+)
+sys.path[:0] = [os.path.join(topsrcdir, p) for p in EXTRA_PATHS]
+
+from mozversioncontrol import InvalidRepoPath, get_repository_object
+
+
+def run_clang_format(hooktype, changedFiles):
+ try:
+ vcs = get_repository_object(topsrcdir)
+ except InvalidRepoPath:
+ return
+
+ if not changedFiles:
+ # No files have been touched
+ return
+
+ # We have also a copy of this list in:
+ # python/mozbuild/mozbuild/mach_commands.py
+ # version-control-tools/hgext/clang-format/__init__.py
+ # release-services/src/staticanalysis/bot/static_analysis_bot/config.py
+ # Too heavy to import the full class just for this variable
+ extensions = (".cpp", ".c", ".cc", ".h", ".m", ".mm")
+ path_list = []
+ for filename in sorted(changedFiles):
+ # Ignore files unsupported in clang-format
+ if filename.endswith(extensions):
+ path_list.append(filename)
+
+ if not path_list:
+ # No files have been touched
+ return
+
+ arguments = ["clang-format", "-p"] + path_list
+ # On windows we need this to call the command in a shell, see Bug 1511594
+ if os.name == "nt":
+ clang_format_cmd = [sys.executable, "mach"] + arguments
+ else:
+ clang_format_cmd = [os.path.join(topsrcdir, "mach")] + arguments
+ if "commit" in hooktype:
+ # don't prevent commits, just display the clang-format results
+ subprocess.call(clang_format_cmd)
+
+ vcs.add_remove_files(*path_list)
+
+ return False
+ print("warning: '{}' is not a valid clang-format hooktype".format(hooktype))
+ return False
+
+
+def hg(ui, repo, node, **kwargs):
+ print(
+ "warning: this hook has been deprecated. Please use the hg extension instead.\n"
+ "please add 'clang-format = ~/.mozbuild/version-control-tools/hgext/clang-format'"
+ " to hgrc\n"
+ "Or run 'mach bootstrap'"
+ )
+ return False
+
+
+def git():
+ hooktype = os.path.basename(__file__)
+ if hooktype == "hooks_clang_format.py":
+ hooktype = "pre-push"
+
+ try:
+ changedFiles = check_output(
+ ["git", "diff", "--staged", "--diff-filter=d", "--name-only", "HEAD"],
+ text=True,
+ ).split()
+ # TODO we should detect if we are in a "add -p" mode and show a warning
+ return run_clang_format(hooktype, changedFiles)
+
+ except CalledProcessError:
+ print("Command to retrieve local files failed")
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(git())
diff --git a/tools/lint/hooks_js_format.py b/tools/lint/hooks_js_format.py
new file mode 100755
index 0000000000..c90315153e
--- /dev/null
+++ b/tools/lint/hooks_js_format.py
@@ -0,0 +1,83 @@
+#!/usr/bin/env python3
+# 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 subprocess
+import sys
+from subprocess import CalledProcessError, check_output
+
+here = os.path.dirname(os.path.realpath(__file__))
+topsrcdir = os.path.join(here, os.pardir, os.pardir)
+
+EXTRA_PATHS = (
+ "python/mach",
+ "python/mozbuild",
+ "python/mozversioncontrol",
+ "testing/mozbase/mozfile",
+ "third_party/python/jsmin",
+)
+sys.path[:0] = [os.path.join(topsrcdir, p) for p in EXTRA_PATHS]
+
+from mozversioncontrol import InvalidRepoPath, get_repository_object
+
+
+def run_js_format(hooktype, changedFiles):
+ try:
+ vcs = get_repository_object(topsrcdir)
+ except InvalidRepoPath:
+ return
+
+ if not changedFiles:
+ # No files have been touched
+ return
+
+ extensions = (".js", ".jsx", ".jsm", ".mjs", "sjs", "html", "xhtml")
+ path_list = []
+ for filename in sorted(changedFiles):
+ # Ignore files unsupported in eslint and prettier
+ if filename.endswith(extensions):
+ path_list.append(filename)
+
+ if not path_list:
+ # No files have been touched
+ return
+
+ arguments = ["eslint", "--fix"] + path_list
+ # On windows we need this to call the command in a shell, see Bug 1511594
+ if os.name == "nt":
+ js_format_cmd = ["sh", "mach"] + arguments
+ else:
+ js_format_cmd = [os.path.join(topsrcdir, "mach")] + arguments
+ if "commit" in hooktype:
+ # don't prevent commits, just display the eslint and prettier results
+ subprocess.call(js_format_cmd)
+
+ vcs.add_remove_files(*path_list)
+
+ return False
+ print("warning: '{}' is not a valid js-format hooktype".format(hooktype))
+ return False
+
+
+def git():
+ hooktype = os.path.basename(__file__)
+ if hooktype == "hooks_js_format.py":
+ hooktype = "pre-push"
+
+ try:
+ changedFiles = check_output(
+ ["git", "diff", "--staged", "--diff-filter=d", "--name-only", "HEAD"],
+ text=True,
+ ).split()
+ # TODO we should detect if we are in a "add -p" mode and show a warning
+ return run_js_format(hooktype, changedFiles)
+
+ except CalledProcessError:
+ print("Command to retrieve local files failed")
+ return 1
+
+
+if __name__ == "__main__":
+ sys.exit(git())
diff --git a/tools/lint/isort.yml b/tools/lint/isort.yml
new file mode 100644
index 0000000000..d15948f65e
--- /dev/null
+++ b/tools/lint/isort.yml
@@ -0,0 +1,13 @@
+---
+isort:
+ description: Sort python imports
+ # Excludes should be added to topsrcdir/.flake8.
+ exclude: []
+ extensions: ['configure', 'py']
+ support-files:
+ - '**/.flake8'
+ - '**/.isort.cfg'
+ - 'tools/lint/python/isort*'
+ type: external
+ payload: python.isort:lint
+ setup: python.isort:setup
diff --git a/tools/lint/l10n.yml b/tools/lint/l10n.yml
new file mode 100644
index 0000000000..d2fc5e5d85
--- /dev/null
+++ b/tools/lint/l10n.yml
@@ -0,0 +1,38 @@
+---
+l10n:
+ description: Localization linter
+ # list of include directories of both
+ # browser and mobile/android l10n.tomls
+ include:
+ - browser/branding/official/locales/en-US
+ - browser/extensions/formautofill/locales/en-US
+ - browser/extensions/report-site-issue/locales/en-US
+ - browser/locales/en-US
+ - devtools/client/locales/en-US
+ - devtools/shared/locales/en-US
+ - devtools/startup/locales/en-US
+ - dom/locales/en-US
+ - mobile/android/locales/en-US
+ - netwerk/locales/en-US
+ - security/manager/locales/en-US
+ - services/sync/locales/en-US
+ - toolkit/locales/en-US
+ - tools/lint/l10n.yml
+ # files not supported by compare-locales,
+ # and also not relevant to this linter
+ exclude:
+ - browser/locales/en-US/firefox-l10n.js
+ - mobile/android/locales/en-US/mobile-l10n.js
+ - toolkit/locales/en-US/chrome/global/intl.css
+ l10n_configs:
+ - browser/locales/l10n.toml
+ - mobile/android/locales/l10n.toml
+ type: external
+ payload: python.l10n_lint:lint
+ setup: python.l10n_lint:gecko_strings_setup
+ support-files:
+ - '**/l10n.toml'
+ - 'third_party/python/compare-locales/**'
+ - 'third_party/python/fluent/**'
+ - 'tools/lint/python/l10n_lint.py'
+ - 'tools/lint/l10n.yml'
diff --git a/tools/lint/libpref/__init__.py b/tools/lint/libpref/__init__.py
new file mode 100644
index 0000000000..e986be39d0
--- /dev/null
+++ b/tools/lint/libpref/__init__.py
@@ -0,0 +1,112 @@
+# -*- Mode: python; c-basic-offset: 4; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 re
+import sys
+
+import yaml
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+# This simple linter checks for duplicates from
+# modules/libpref/init/StaticPrefList.yaml against modules/libpref/init/all.js
+
+# If for any reason a pref needs to appear in both files, add it to this set.
+IGNORE_PREFS = {
+ "devtools.console.stdout.chrome", # Uses the 'sticky' attribute.
+ "devtools.console.stdout.content", # Uses the 'sticky' attribute.
+ "fission.autostart", # Uses the 'locked' attribute.
+ "browser.dom.window.dump.enabled", # Uses the 'sticky' attribute.
+ "apz.fling_curve_function_y2", # This pref is a part of a series.
+ "dom.postMessage.sharedArrayBuffer.bypassCOOP_COEP.insecure.enabled", # NOQA: E501; Uses the 'locked' attribute.
+}
+PATTERN = re.compile(r"\s*pref\(\s*\"(?P<pref>.+)\"\s*,\s*(?P<val>.+)\)\s*;.*")
+
+
+def get_names(pref_list_filename):
+ pref_names = {}
+ # We want to transform patterns like 'foo: @VAR@' into valid yaml. This
+ # pattern does not happen in 'name', so it's fine to ignore these.
+ # We also want to evaluate all branches of #ifdefs for pref names, so we
+ # ignore anything else preprocessor related.
+ file = open(pref_list_filename).read().replace("@", "")
+ try:
+ pref_list = yaml.safe_load(file)
+ except (IOError, ValueError) as e:
+ print("{}: error:\n {}".format(pref_list_filename, e), file=sys.stderr)
+ sys.exit(1)
+
+ for pref in pref_list:
+ if pref["name"] not in IGNORE_PREFS:
+ pref_names[pref["name"]] = pref["value"]
+
+ return pref_names
+
+
+# Check the names of prefs against each other, and if the pref is a duplicate
+# that has not previously been noted, add that name to the list of errors.
+def check_against(path, pref_names):
+ errors = []
+ prefs = read_prefs(path)
+ for pref in prefs:
+ if pref["name"] in pref_names:
+ errors.extend(check_value_for_pref(pref, pref_names[pref["name"]], path))
+ return errors
+
+
+def check_value_for_pref(some_pref, some_value, path):
+ errors = []
+ if some_pref["value"] == some_value:
+ errors.append(
+ {
+ "path": path,
+ "message": some_pref["raw"],
+ "lineno": some_pref["line"],
+ "hint": "Remove the duplicate pref or add it to IGNORE_PREFS.",
+ "level": "error",
+ }
+ )
+ return errors
+
+
+# The entries in the *.js pref files are regular enough to use simple pattern
+# matching to load in prefs.
+def read_prefs(path):
+ prefs = []
+ with open(path) as source:
+ for lineno, line in enumerate(source, start=1):
+ match = PATTERN.match(line)
+ if match:
+ prefs.append(
+ {
+ "name": match.group("pref"),
+ "value": evaluate_pref(match.group("val")),
+ "line": lineno,
+ "raw": line,
+ }
+ )
+ return prefs
+
+
+def evaluate_pref(value):
+ bools = {"true": True, "false": False}
+ if value in bools:
+ return bools[value]
+ elif value.isdigit():
+ return int(value)
+ return value
+
+
+def checkdupes(paths, config, **kwargs):
+ results = []
+ errors = []
+ pref_names = get_names(config["support-files"][0])
+ files = list(expand_exclusions(paths, config, kwargs["root"]))
+ for file in files:
+ errors.extend(check_against(file, pref_names))
+ for error in errors:
+ results.append(result.from_config(config, **error))
+ return results
diff --git a/tools/lint/license.yml b/tools/lint/license.yml
new file mode 100644
index 0000000000..f6bd6576ac
--- /dev/null
+++ b/tools/lint/license.yml
@@ -0,0 +1,94 @@
+---
+license:
+ description: License Check
+ include:
+ - .
+ exclude:
+ # These paths need to be triaged.
+ - build/pgo/js-input
+ # License not super clear
+ - browser/branding/
+ # Trademarks
+ - browser/components/pocket/content/panels/
+ - browser/components/newtab/data/content/tippytop/images/
+ - toolkit/components/pdfjs/content/web/images/
+ # We probably want a specific license
+ - browser/extensions/webcompat/injections/
+ # Different license
+ - build/pgo/blueprint/print.css
+ # Different license
+ - build/pgo/blueprint/screen.css
+ # Empty files
+ - config/external/nspr/_pl_bld.h
+ - config/external/nspr/_pr_bld.h
+ # Unknown origin
+ - gfx/2d/MMIHelpers.h
+ # might not work with license
+ - gradle.properties
+ # might not work with license
+ - gradle/wrapper/gradle-wrapper.properties
+ # Imported code that is dual Apache2 / MIT licensed
+ - intl/l10n/rust/l10nregistry-rs
+ # tests
+ - js/src/devtools/rootAnalysis/t/
+ - mobile/android/geckoview/src/main/AndroidManifest.xml
+ - mobile/android/geckoview/src/main/AndroidManifest_overlay.jinja
+ - mobile/android/geckoview/src/main/res/drawable/ic_generic_file.xml
+ - mobile/android/geckoview_example/src/main
+ - testing/webcompat/interventions/
+ - testing/webcompat/shims/
+ # might not work with license
+ - mobile/android/gradle/dotgradle-offline/gradle.properties
+ # might not work with license
+ - mobile/android/gradle/dotgradle-online/gradle.properties
+ # Almost empty file
+ - modules/libpref/greprefs.js
+ - parser/html/java/named-character-references.html
+ - python/mozlint/test/files/
+ # By design
+ - python/mozrelease/mozrelease
+ - security/mac/hardenedruntime/browser.developer.entitlements.xml
+ - security/mac/hardenedruntime/browser.production.entitlements.xml
+ - security/mac/hardenedruntime/developer.entitlements.xml
+ - security/mac/hardenedruntime/plugin-container.developer.entitlements.xml
+ - security/mac/hardenedruntime/plugin-container.production.entitlements.xml
+ - security/mac/hardenedruntime/production.entitlements.xml
+ - testing/marionette/harness/marionette_harness/www/
+ # Browsertime can't handle this script when there's a comment at the top
+ - testing/raptor/browsertime/browsertime_benchmark.js
+ - toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.pb.cc
+ - toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.pb.h
+ - toolkit/mozapps/update/updater/crctable.h
+ - tools/lint/eslint/eslint-plugin-mozilla/lib/configs
+ # template fragments used to generate .js sources.
+ - toolkit/components/uniffi-bindgen-gecko-js/src/templates/js
+ # By design
+ - tools/lint/test/
+ extensions:
+ - .c
+ - .cc
+ - .cpp
+ - .css
+ - .dtd
+ - .ftl
+ - .h
+ - .html
+ - .java
+ - .js
+ - .jsm
+ - .jsx
+ - .m
+ - .mm
+ - .mjs
+ - .properties
+ - .py
+ - .rs
+ - .svg
+ - .xhtml
+ - .xml
+ - .xul
+ support-files:
+ - 'tools/lint/license/**'
+ type: external
+ payload: license:lint
+ find-dotfiles: true
diff --git a/tools/lint/license/__init__.py b/tools/lint/license/__init__.py
new file mode 100644
index 0000000000..25139b49c5
--- /dev/null
+++ b/tools/lint/license/__init__.py
@@ -0,0 +1,195 @@
+# 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
+
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+here = os.path.abspath(os.path.dirname(__file__))
+
+results = []
+
+# Official source: https://www.mozilla.org/en-US/MPL/headers/
+TEMPLATES = {
+ "mpl2_license": """
+ 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/.
+ """.strip().splitlines(),
+ "public_domain_license": """
+ Any copyright is dedicated to the public domain.
+ http://creativecommons.org/publicdomain/zero/1.0/
+ """.strip().splitlines(),
+}
+license_list = os.path.join(here, "valid-licenses.txt")
+
+
+def load_valid_license():
+ """
+ Load the list of license patterns
+ """
+ with open(license_list) as f:
+ l = f.readlines()
+ # Remove the empty lines
+ return list(filter(bool, [x.replace("\n", "") for x in l]))
+
+
+def is_valid_license(licenses, filename):
+ """
+ From a given file, check if we can find the license patterns
+ in the X first lines of the file
+ """
+ with open(filename, "r", errors="replace") as myfile:
+ contents = myfile.read()
+ # Empty files don't need a license.
+ if not contents:
+ return True
+
+ for l in licenses:
+ if l.lower().strip() in contents.lower():
+ return True
+ return False
+
+
+def add_header(log, filename, header):
+ """
+ Add the header to the top of the file
+ """
+ header.append("\n")
+ with open(filename, "r+") as f:
+ # lines in list format
+ try:
+ lines = f.readlines()
+ except UnicodeDecodeError as e:
+ log.debug("Could not read file '{}'".format(f))
+ log.debug("Error: {}".format(e))
+ return
+
+ i = 0
+ if lines:
+ # if the file isn't empty (__init__.py can be empty files)
+ if lines[0].startswith("#!") or lines[0].startswith("<?xml "):
+ i = 1
+
+ if lines[0].startswith("/* -*- Mode"):
+ i = 2
+ # Insert in the top of the data structure
+ lines[i:i] = header
+ f.seek(0, 0)
+ f.write("".join(lines))
+
+
+def is_test(f):
+ """
+ is the file a test or not?
+ """
+ if "lint/test/" in f:
+ # For the unit tests
+ return False
+ return (
+ "/tests/" in f
+ or "/test/" in f
+ or "/test_" in f
+ or "/gtest" in f
+ or "/crashtest" in f
+ or "/mochitest" in f
+ or "/reftest" in f
+ or "/imptest" in f
+ or "/androidTest" in f
+ or "/jit-test/" in f
+ or "jsapi-tests/" in f
+ )
+
+
+def fix_me(log, filename):
+ """
+ Add the copyright notice to the top of the file
+ """
+ _, ext = os.path.splitext(filename)
+ license = []
+
+ license_template = TEMPLATES["mpl2_license"]
+ test = False
+
+ if is_test(filename):
+ license_template = TEMPLATES["public_domain_license"]
+ test = True
+
+ if ext in [
+ ".cpp",
+ ".c",
+ ".cc",
+ ".h",
+ ".m",
+ ".mm",
+ ".rs",
+ ".java",
+ ".js",
+ ".jsm",
+ ".jsx",
+ ".css",
+ ]:
+ for i, l in enumerate(license_template):
+ start = " "
+ end = ""
+ if i == 0:
+ # first line, we have the /*
+ start = "/"
+ if i == len(license_template) - 1:
+ # Last line, we end by */
+ end = " */"
+ license.append(start + "* " + l.strip() + end + "\n")
+
+ add_header(log, filename, license)
+ return
+
+ if ext in [".py", ".ftl", ".properties"] or filename.endswith(".inc.xul"):
+ for l in license_template:
+ license.append("# " + l.strip() + "\n")
+ add_header(log, filename, license)
+ return
+
+ if ext in [".xml", ".xul", ".html", ".xhtml", ".dtd", ".svg"]:
+ for i, l in enumerate(license_template):
+ start = " - "
+ end = ""
+ if i == 0:
+ # first line, we have the <!--
+ start = "<!-- "
+ if i == 2 or (i == 1 and test):
+ # Last line, we end by -->
+ end = " -->"
+ license.append(start + l.strip() + end)
+ if ext != ".svg" or end == "":
+ # When dealing with an svg, we should not have a space between
+ # the license and the content
+ license.append("\n")
+ add_header(log, filename, license)
+ return
+
+
+def lint(paths, config, fix=None, **lintargs):
+ log = lintargs["log"]
+ files = list(expand_exclusions(paths, config, lintargs["root"]))
+ fixed = 0
+
+ licenses = load_valid_license()
+ for f in files:
+ if is_test(f):
+ # For now, do not do anything with test (too many)
+ continue
+
+ if not is_valid_license(licenses, f):
+ res = {
+ "path": f,
+ "message": "No matching license strings found in tools/lint/license/valid-licenses.txt", # noqa
+ "level": "error",
+ }
+ results.append(result.from_config(config, **res))
+ if fix:
+ fix_me(log, f)
+ fixed += 1
+
+ return {"results": results, "fixed": fixed}
diff --git a/tools/lint/license/valid-licenses.txt b/tools/lint/license/valid-licenses.txt
new file mode 100644
index 0000000000..6518f347c4
--- /dev/null
+++ b/tools/lint/license/valid-licenses.txt
@@ -0,0 +1,29 @@
+mozilla.org/MPL/
+Licensed under the Apache License, Version 2.0
+copyright is dedicated to the Public Domain.
+under the MIT
+Redistributions of source code must retain the above copyright
+Use of this source code is governed by a BSD-style license
+The author disclaims copyright to this source code.
+The author hereby disclaims copyright to this source code
+Use, Modification and Redistribution (including distribution of any
+author grants irrevocable permission to anyone to use, modify,
+THIS FILE IS AUTO-GENERATED
+Permission is hereby granted, free of charge, to any person obtaining
+Permission to use, copy, modify,
+License: Public domain. You are free to use this code however you
+You are granted a license to use, reproduce and create derivative works
+GENERATED FILE, DO NOT EDIT
+This code is governed by the BSD license
+This Source Code Form is subject to the terms of the Apache License
+DO NOT EDIT
+This program is made available under an ISC-style license.
+under MIT license
+License MIT per upstream
+DO NOT MODIFY
+GNU General Public License
+FILE IS GENERATED
+Generated by
+do not edit
+may be protected as a trademark in some jurisdictions
+The ASF licenses this file to You under the Apache License, Version 2.0
diff --git a/tools/lint/lintpref.yml b/tools/lint/lintpref.yml
new file mode 100644
index 0000000000..cb58d642bc
--- /dev/null
+++ b/tools/lint/lintpref.yml
@@ -0,0 +1,17 @@
+---
+lintpref:
+ description: Linter for static prefs.
+ include:
+ - 'modules/libpref/init'
+ - 'browser/app/profile/'
+ - 'mobile/android/app/'
+ - 'devtools/client/preferences/'
+ - 'browser/branding/'
+ - 'mobile/android/installer/'
+ - 'mobile/android/locales/'
+ exclude: []
+ extensions: ['js']
+ type: external
+ payload: libpref:checkdupes
+ support-files:
+ - 'modules/libpref/init/StaticPrefList.yaml'
diff --git a/tools/lint/mach_commands.py b/tools/lint/mach_commands.py
new file mode 100644
index 0000000000..c9778e28a0
--- /dev/null
+++ b/tools/lint/mach_commands.py
@@ -0,0 +1,189 @@
+# 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
+ )
diff --git a/tools/lint/mingw-capitalization.yml b/tools/lint/mingw-capitalization.yml
new file mode 100644
index 0000000000..04c9c79e16
--- /dev/null
+++ b/tools/lint/mingw-capitalization.yml
@@ -0,0 +1,12 @@
+---
+mingw-capitalization:
+ description: >
+ "A Windows include file is not lowercase, and may break the MinGW build"
+ extensions: ['h', 'cpp', 'cc', 'c']
+ include: ['.']
+ exclude:
+ # We do not compile WebRTC with MinGW yet
+ - media/webrtc
+ type: external
+ level: error
+ payload: cpp.mingw-capitalization:lint
diff --git a/tools/lint/mscom-init.yml b/tools/lint/mscom-init.yml
new file mode 100644
index 0000000000..8c0af6ffcf
--- /dev/null
+++ b/tools/lint/mscom-init.yml
@@ -0,0 +1,46 @@
+# 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/.
+---
+forbid-mscom-init:
+ description: >-
+ New calls to CoInitialize, CoInitializeEx, OleInitialize, RoInitialize,
+ CoUninitialize, OleUninitialize, or RoUninitialize are forbidden. If you
+ have questions, please consult a peer of the IPC: MSCOM module.
+ level: error
+ include: ['.']
+ type: regex
+ payload: ([CR]o|Ole)(Uni|I)nitialize(Ex)?
+ ignore-case: false
+ extensions:
+ - h
+ - c
+ - cc
+ - cpp
+ exclude:
+ # These files are the only allowable locations
+ - ipc/mscom/ApartmentRegion.h
+ - ipc/mscom/COMWrappers.cpp
+ - ipc/mscom/COMWrappers.h
+ - ipc/mscom/ProcessRuntime.cpp
+ - ipc/mscom/EnsureMTA.cpp
+ # These files are existing locations that must eventually be fixed.
+ - browser/components/migration/nsIEHistoryEnumerator.cpp
+ - browser/components/migration/tests/unit/insertIEHistory/InsertIEHistory.cpp
+ - browser/components/shell/nsWindowsShellService.cpp
+ - gfx/thebes/gfxWindowsPlatform.cpp
+ - image/DecodePool.cpp
+ - ipc/glue/BrowserProcessSubThread.cpp
+ - netwerk/system/win32/nsNotifyAddrListener.cpp
+ - toolkit/components/parentalcontrols/nsParentalControlsServiceWin.cpp
+ - toolkit/crashreporter/google-breakpad/src/common/windows/pdb_source_line_writer.cc
+ - toolkit/mozapps/defaultagent/main.cpp
+ - uriloader/exthandler/win/nsOSHelperAppService.cpp
+ - widget/windows/InkCollector.cpp
+ - widget/windows/TaskbarPreview.cpp
+ - widget/windows/WinTaskbar.cpp
+ - widget/windows/nsAppShell.cpp
+ - widget/windows/nsWindow.cpp
+ - widget/windows/nsWindow.h
+ - widget/windows/tests/TestUriValidation.cpp
+ - xpcom/io/nsLocalFileWin.cpp
diff --git a/tools/lint/perfdocs.yml b/tools/lint/perfdocs.yml
new file mode 100644
index 0000000000..3aca2c62c5
--- /dev/null
+++ b/tools/lint/perfdocs.yml
@@ -0,0 +1,16 @@
+---
+perfdocs:
+ description: Performance Documentation linter
+ # This task handles its own search, so just include cwd
+ include: [
+ 'testing/raptor',
+ 'python/mozperftest',
+ 'testing/talos',
+ 'testing/awsy',
+ 'testing/fxrecord',
+ ]
+ exclude: []
+ extensions: ['rst', 'ini', 'yml']
+ support-files: []
+ type: structured_log
+ payload: perfdocs:lint
diff --git a/tools/lint/perfdocs/__init__.py b/tools/lint/perfdocs/__init__.py
new file mode 100644
index 0000000000..1194d38624
--- /dev/null
+++ b/tools/lint/perfdocs/__init__.py
@@ -0,0 +1,13 @@
+# 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
+
+from perfdocs import perfdocs
+
+here = os.path.abspath(os.path.dirname(__file__))
+PERFDOCS_REQUIREMENTS_PATH = os.path.join(here, "requirements.txt")
+
+
+def lint(paths, config, logger, fix=False, **lintargs):
+ return perfdocs.run_perfdocs(config, logger=logger, paths=paths, generate=fix)
diff --git a/tools/lint/perfdocs/doc_helpers.py b/tools/lint/perfdocs/doc_helpers.py
new file mode 100644
index 0000000000..709f190204
--- /dev/null
+++ b/tools/lint/perfdocs/doc_helpers.py
@@ -0,0 +1,85 @@
+# 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/.
+
+
+class MismatchedRowLengthsException(Exception):
+ """
+ This exception is thrown when there is a mismatch between the number of items in a row,
+ and the number of headers defined.
+ """
+
+ pass
+
+
+class TableBuilder(object):
+ """
+ Helper class for building tables.
+ """
+
+ def __init__(self, title, widths, header_rows, headers, indent=0):
+ """
+ :param title: str - Title of the table
+ :param widths: list of str - Widths of each column of the table
+ :param header_rows: int - Number of header rows
+ :param headers: 2D list of str - Headers
+ :param indent: int - Number of spaces to indent table
+ """
+ if not isinstance(title, str):
+ raise TypeError("TableBuilder attribute title must be a string.")
+ if not isinstance(widths, list) or not isinstance(widths[0], int):
+ raise TypeError("TableBuilder attribute widths must be a list of integers.")
+ if not isinstance(header_rows, int):
+ raise TypeError("TableBuilder attribute header_rows must be an integer.")
+ if (
+ not isinstance(headers, list)
+ or not isinstance(headers[0], list)
+ or not isinstance(headers[0][0], str)
+ ):
+ raise TypeError(
+ "TableBuilder attribute headers must be a two-dimensional list of strings."
+ )
+ if not isinstance(indent, int):
+ raise TypeError("TableBuilder attribute indent must be an integer.")
+
+ self.title = title
+ self.widths = widths
+ self.header_rows = header_rows
+ self.headers = headers
+ self.indent = " " * indent
+ self.table = ""
+ self._build_table()
+
+ def _build_table(self):
+ if len(self.widths) != len(self.headers[0]):
+ raise MismatchedRowLengthsException(
+ "Number of table headers must match number of column widths."
+ )
+ widths = " ".join(map(str, self.widths))
+ self.table += (
+ f"{self.indent}.. list-table:: **{self.title}**\n"
+ f"{self.indent} :widths: {widths}\n"
+ f"{self.indent} :header-rows: {self.header_rows}\n\n"
+ )
+ self.add_rows(self.headers)
+
+ def add_rows(self, rows):
+ if type(rows) != list or type(rows[0]) != list or type(rows[0][0]) != str:
+ raise TypeError("add_rows() requires a two-dimensional list of strings.")
+ for row in rows:
+ self.add_row(row)
+
+ def add_row(self, values):
+ if len(values) != len(self.widths):
+ raise MismatchedRowLengthsException(
+ "Number of items in a row must must number of columns defined."
+ )
+ for i, val in enumerate(values):
+ if i == 0:
+ self.table += f"{self.indent} * - **{val}**\n"
+ else:
+ self.table += f"{self.indent} - {val}\n"
+
+ def finish_table(self):
+ self.table += "\n"
+ return self.table
diff --git a/tools/lint/perfdocs/framework_gatherers.py b/tools/lint/perfdocs/framework_gatherers.py
new file mode 100644
index 0000000000..c85690879b
--- /dev/null
+++ b/tools/lint/perfdocs/framework_gatherers.py
@@ -0,0 +1,569 @@
+# 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 json
+import os
+import pathlib
+import re
+
+from gecko_taskgraph.util.attributes import match_run_on_projects
+from manifestparser import TestManifest
+from mozperftest.script import ScriptInfo
+from perfdocs.doc_helpers import TableBuilder
+from perfdocs.logger import PerfDocLogger
+from perfdocs.utils import read_yaml
+
+logger = PerfDocLogger()
+
+BRANCHES = [
+ "mozilla-central",
+ "autoland",
+ "mozilla-release",
+ "mozilla-beta",
+]
+
+"""
+This file is for framework specific gatherers since manifests
+might be parsed differently in each of them. The gatherers
+must implement the FrameworkGatherer class.
+"""
+
+
+class FrameworkGatherer(object):
+ """
+ Abstract class for framework gatherers.
+ """
+
+ def __init__(self, yaml_path, workspace_dir, taskgraph={}):
+ """
+ Generic initialization for a framework gatherer.
+ """
+ self.workspace_dir = workspace_dir
+ self._yaml_path = yaml_path
+ self._taskgraph = taskgraph
+ self._suite_list = {}
+ self._test_list = {}
+ self._descriptions = {}
+ self._manifest_path = ""
+ self._manifest = None
+ self.script_infos = {}
+ self._task_list = {}
+ self._task_match_pattern = re.compile(r"([\w\W]*/[pgo|opt]*)-([\w\W]*)")
+
+ def get_task_match(self, task_name):
+ return re.search(self._task_match_pattern, task_name)
+
+ def get_manifest_path(self):
+ """
+ Returns the path to the manifest based on the
+ manifest entry in the frameworks YAML configuration
+ file.
+
+ :return str: Path to the manifest.
+ """
+ if self._manifest_path:
+ return self._manifest_path
+
+ yaml_content = read_yaml(self._yaml_path)
+ self._manifest_path = pathlib.Path(self.workspace_dir, yaml_content["manifest"])
+ return self._manifest_path
+
+ def get_suite_list(self):
+ """
+ Each framework gatherer must return a dictionary with
+ the following structure. Note that the test names must
+ be relative paths so that issues can be correctly issued
+ by the reviewbot.
+
+ :return dict: A dictionary with the following structure: {
+ "suite_name": [
+ 'testing/raptor/test1',
+ 'testing/raptor/test2'
+ ]
+ }
+ """
+ raise NotImplementedError
+
+ def _build_section_with_header(self, title, content, header_type=None):
+ """
+ Adds a section to the documentation with the title as the type mentioned
+ and paragraph as content mentioned.
+ :param title: title of the section
+ :param content: content of section paragraph
+ :param header_type: type of the title heading
+ """
+ heading_map = {"H2": "*", "H3": "=", "H4": "-", "H5": "^"}
+ return [title, heading_map.get(header_type, "^") * len(title), content, ""]
+
+
+class RaptorGatherer(FrameworkGatherer):
+ """
+ Gatherer for the Raptor framework.
+ """
+
+ def get_suite_list(self):
+ """
+ Returns a dictionary containing a mapping from suites
+ to the tests they contain.
+
+ :return dict: A dictionary with the following structure: {
+ "suite_name": [
+ 'testing/raptor/test1',
+ 'testing/raptor/test2'
+ ]
+ }
+ """
+ if self._suite_list:
+ return self._suite_list
+
+ manifest_path = self.get_manifest_path()
+
+ # Get the tests from the manifest
+ test_manifest = TestManifest([str(manifest_path)], strict=False)
+ test_list = test_manifest.active_tests(exists=False, disabled=False)
+
+ # Parse the tests into the expected dictionary
+ for test in test_list:
+ # Get the top-level suite
+ s = os.path.basename(test["here"])
+ if s not in self._suite_list:
+ self._suite_list[s] = []
+
+ # Get the individual test
+ fpath = re.sub(".*testing", "testing", test["manifest"])
+
+ if fpath not in self._suite_list[s]:
+ self._suite_list[s].append(fpath)
+
+ return self._suite_list
+
+ def _get_ci_tasks(self):
+ for task in self._taskgraph.keys():
+ if type(self._taskgraph[task]) == dict:
+ command = self._taskgraph[task]["task"]["payload"].get("command", [])
+ run_on_projects = self._taskgraph[task]["attributes"]["run_on_projects"]
+ else:
+ command = self._taskgraph[task].task["payload"].get("command", [])
+ run_on_projects = self._taskgraph[task].attributes["run_on_projects"]
+
+ test_match = re.search(r"[\s']--test[\s=](.+?)[\s']", str(command))
+ task_match = self.get_task_match(task)
+ if test_match and task_match:
+ test = test_match.group(1)
+ platform = task_match.group(1)
+ test_name = task_match.group(2)
+
+ item = {"test_name": test_name, "run_on_projects": run_on_projects}
+ self._task_list.setdefault(test, {}).setdefault(platform, []).append(
+ item
+ )
+
+ def _get_subtests_from_ini(self, manifest_path, suite_name):
+ """
+ Returns a list of (sub)tests from an ini file containing the test definitions.
+
+ :param str manifest_path: path to the ini file
+ :return list: the list of the tests
+ """
+ desc_exclusion = ["here", "manifest_relpath", "path", "relpath"]
+ test_manifest = TestManifest([str(manifest_path)], strict=False)
+ test_list = test_manifest.active_tests(exists=False, disabled=False)
+ subtests = {}
+ for subtest in test_list:
+ subtests[subtest["name"]] = subtest["manifest"]
+
+ description = {}
+ for key, value in subtest.items():
+ if key not in desc_exclusion:
+ description[key] = value
+
+ # Prepare alerting metrics for verification
+ description["metrics"] = [
+ metric.strip()
+ for metric in description.get("alert_on", "").split(",")
+ if metric.strip() != ""
+ ]
+
+ subtests[subtest["name"]] = description
+ self._descriptions.setdefault(suite_name, []).append(description)
+
+ self._descriptions[suite_name].sort(key=lambda item: item["name"])
+
+ return subtests
+
+ def get_test_list(self):
+ """
+ Returns a dictionary containing the tests in every suite ini file.
+
+ :return dict: A dictionary with the following structure: {
+ "suite_name": {
+ 'raptor_test1',
+ 'raptor_test2'
+ },
+ }
+ """
+ if self._test_list:
+ return self._test_list
+
+ suite_list = self.get_suite_list()
+
+ # Iterate over each manifest path from suite_list[suite_name]
+ # and place the subtests into self._test_list under the same key
+ for suite_name, manifest_paths in suite_list.items():
+ if not self._test_list.get(suite_name):
+ self._test_list[suite_name] = {}
+ for manifest_path in manifest_paths:
+ subtest_list = self._get_subtests_from_ini(manifest_path, suite_name)
+ self._test_list[suite_name].update(subtest_list)
+
+ self._get_ci_tasks()
+
+ return self._test_list
+
+ def build_test_description(self, title, test_description="", suite_name=""):
+ matcher = []
+ browsers = [
+ "firefox",
+ "chrome",
+ "chromium",
+ "refbrow",
+ "fennec68",
+ "geckoview",
+ "fenix",
+ ]
+ test_name = [f"{title}-{browser}" for browser in browsers]
+ test_name.append(title)
+
+ for suite, val in self._descriptions.items():
+ for test in val:
+ if test["name"] in test_name and suite_name == suite:
+ matcher.append(test)
+
+ if len(matcher) == 0:
+ logger.critical(
+ "No tests exist for the following name "
+ "(obtained from config.yml): {}".format(title)
+ )
+ raise Exception(
+ "No tests exist for the following name "
+ "(obtained from config.yml): {}".format(title)
+ )
+
+ result = f".. dropdown:: {title}\n"
+ result += f" :container: + anchor-id-{title}-{suite_name[0]}\n\n"
+
+ for idx, description in enumerate(matcher):
+ if description["name"] != title:
+ result += f" {idx+1}. **{description['name']}**\n\n"
+ if "owner" in description.keys():
+ result += f" **Owner**: {description['owner']}\n\n"
+
+ for key in sorted(description.keys()):
+ if key in ["owner", "name", "manifest", "metrics"]:
+ continue
+ sub_title = key.replace("_", " ")
+ if key == "test_url":
+ if "<" in description[key] or ">" in description[key]:
+ description[key] = description[key].replace("<", "\<")
+ description[key] = description[key].replace(">", "\>")
+ result += f" * **{sub_title}**: `<{description[key]}>`__\n"
+ elif key == "secondary_url":
+ result += f" * **{sub_title}**: `<{description[key]}>`__\n"
+ elif key in ["playback_pageset_manifest"]:
+ result += (
+ f" * **{sub_title}**: "
+ f"{description[key].replace('{subtest}', description['name'])}\n"
+ )
+ else:
+ if "\n" in description[key]:
+ description[key] = description[key].replace("\n", " ")
+ result += f" * **{sub_title}**: {description[key]}\n"
+
+ if self._task_list.get(title, []):
+ result += " * **Test Task**:\n\n"
+ for platform in sorted(self._task_list[title]):
+ self._task_list[title][platform].sort(key=lambda x: x["test_name"])
+
+ table = TableBuilder(
+ title=platform,
+ widths=[30] + [15 for x in BRANCHES],
+ header_rows=1,
+ headers=[["Test Name"] + BRANCHES],
+ indent=3,
+ )
+
+ for task in self._task_list[title][platform]:
+ values = [task["test_name"]]
+ values += [
+ "\u2705"
+ if match_run_on_projects(x, task["run_on_projects"])
+ else "\u274C"
+ for x in BRANCHES
+ ]
+ table.add_row(values)
+ result += f"{table.finish_table()}\n"
+
+ return [result]
+
+ def build_suite_section(self, title, content):
+ return self._build_section_with_header(
+ title.capitalize(), content, header_type="H4"
+ )
+
+
+class MozperftestGatherer(FrameworkGatherer):
+ """
+ Gatherer for the Mozperftest framework.
+ """
+
+ def get_test_list(self):
+ """
+ Returns a dictionary containing the tests that are in perftest.ini manifest.
+
+ :return dict: A dictionary with the following structure: {
+ "suite_name": {
+ 'perftest_test1',
+ 'perftest_test2',
+ },
+ }
+ """
+ for path in pathlib.Path(self.workspace_dir).rglob("perftest.ini"):
+ if "obj-" in str(path):
+ continue
+ suite_name = str(path.parent).replace(str(self.workspace_dir), "")
+
+ # If the workspace dir doesn't end with a forward-slash,
+ # the substitution above won't work completely
+ if suite_name.startswith("/") or suite_name.startswith("\\"):
+ suite_name = suite_name[1:]
+
+ # We have to add new paths to the logger as we search
+ # because mozperftest tests exist in multiple places in-tree
+ PerfDocLogger.PATHS.append(suite_name)
+
+ # Get the tests from perftest.ini
+ test_manifest = TestManifest([str(path)], strict=False)
+ test_list = test_manifest.active_tests(exists=False, disabled=False)
+ for test in test_list:
+ si = ScriptInfo(test["path"])
+ self.script_infos[si["name"]] = si
+ self._test_list.setdefault(suite_name.replace("\\", "/"), {}).update(
+ {si["name"]: {"path": str(path)}}
+ )
+
+ return self._test_list
+
+ def build_test_description(self, title, test_description="", suite_name=""):
+ return [str(self.script_infos[title])]
+
+ def build_suite_section(self, title, content):
+ return self._build_section_with_header(title, content, header_type="H4")
+
+
+class TalosGatherer(FrameworkGatherer):
+ def _get_ci_tasks(self):
+ with open(
+ pathlib.Path(self.workspace_dir, "testing", "talos", "talos.json")
+ ) as f:
+ config_suites = json.load(f)["suites"]
+
+ for task_name in self._taskgraph.keys():
+ task = self._taskgraph[task_name]
+
+ if type(task) == dict:
+ is_talos = task["task"]["extra"].get("suite", [])
+ command = task["task"]["payload"].get("command", [])
+ run_on_projects = task["attributes"]["run_on_projects"]
+ else:
+ is_talos = task.task["extra"].get("suite", [])
+ command = task.task["payload"].get("command", [])
+ run_on_projects = task.attributes["run_on_projects"]
+
+ suite_match = re.search(r"[\s']--suite[\s=](.+?)[\s']", str(command))
+ task_match = self.get_task_match(task_name)
+ if "talos" == is_talos and task_match:
+ suite = suite_match.group(1)
+ platform = task_match.group(1)
+ test_name = task_match.group(2)
+ item = {"test_name": test_name, "run_on_projects": run_on_projects}
+
+ for test in config_suites[suite]["tests"]:
+ self._task_list.setdefault(test, {}).setdefault(
+ platform, []
+ ).append(item)
+
+ def get_test_list(self):
+ from talos import test as talos_test
+
+ test_lists = talos_test.test_dict()
+ mod = __import__("talos.test", fromlist=test_lists)
+
+ suite_name = "Talos Tests"
+
+ for test in test_lists:
+ self._test_list.setdefault(suite_name, {}).update({test: {}})
+
+ klass = getattr(mod, test)
+ self._descriptions.setdefault(test, klass.__dict__)
+
+ self._get_ci_tasks()
+
+ return self._test_list
+
+ def build_test_description(self, title, test_description="", suite_name=""):
+ result = f".. dropdown:: {title}\n"
+ result += f" :container: + anchor-id-{title}\n\n"
+
+ yml_descriptions = [s.strip() for s in test_description.split("- ") if s]
+ for description in yml_descriptions:
+ if "Example Data" in description:
+ # Example Data for using code block
+ example_list = [s.strip() for s in description.split("* ")]
+ result += f" * {example_list[0]}\n"
+ result += " .. code-block:: None\n\n"
+ for example in example_list[1:]:
+ result += f" {example}\n"
+
+ elif " * " in description:
+ # Sub List
+ sub_list = [s.strip() for s in description.split(" * ")]
+ result += f" * {sub_list[0]}\n"
+ for sub in sub_list[1:]:
+ result += f" * {sub}\n"
+
+ else:
+ # General List
+ result += f" * {description}\n"
+
+ if title in self._descriptions:
+ for key in sorted(self._descriptions[title]):
+ if key.startswith("__") and key.endswith("__"):
+ continue
+ elif key == "filters":
+ continue
+
+ # On windows, we get the paths in the wrong style
+ value = self._descriptions[title][key]
+ if isinstance(value, dict):
+ for k, v in value.items():
+ if isinstance(v, str) and "\\" in v:
+ value[k] = str(v).replace("\\", r"/")
+ result += r" * " + key + r": " + str(value) + r"\n"
+
+ # Command
+ result += " * Command\n"
+ result += " .. code-block:: None\n\n"
+ result += f" ./mach talos-test -a {title}\n"
+
+ if self._task_list.get(title, []):
+ result += " * **Test Task**:\n\n"
+ for platform in sorted(self._task_list[title]):
+ self._task_list[title][platform].sort(key=lambda x: x["test_name"])
+
+ table = TableBuilder(
+ title=platform,
+ widths=[30] + [15 for x in BRANCHES],
+ header_rows=1,
+ headers=[["Test Name"] + BRANCHES],
+ indent=3,
+ )
+
+ for task in self._task_list[title][platform]:
+ values = [task["test_name"]]
+ values += [
+ "\u2705"
+ if match_run_on_projects(x, task["run_on_projects"])
+ else "\u274C"
+ for x in BRANCHES
+ ]
+ table.add_row(values)
+ result += f"{table.finish_table()}\n"
+
+ return [result]
+
+ def build_suite_section(self, title, content):
+ return self._build_section_with_header(title, content, header_type="H2")
+
+
+class AwsyGatherer(FrameworkGatherer):
+ """
+ Gatherer for the Awsy framework.
+ """
+
+ def _generate_ci_tasks(self):
+ for task_name in self._taskgraph.keys():
+ task = self._taskgraph[task_name]
+
+ if type(task) == dict:
+ awsy_test = task["task"]["extra"].get("suite", [])
+ run_on_projects = task["attributes"]["run_on_projects"]
+ else:
+ awsy_test = task.task["extra"].get("suite", [])
+ run_on_projects = task.attributes["run_on_projects"]
+
+ task_match = self.get_task_match(task_name)
+
+ if "awsy" in awsy_test and task_match:
+ platform = task_match.group(1)
+ test_name = task_match.group(2)
+ item = {"test_name": test_name, "run_on_projects": run_on_projects}
+ self._task_list.setdefault(platform, []).append(item)
+
+ def get_suite_list(self):
+ self._suite_list = {"Awsy tests": ["tp6", "base", "dmd", "tp5"]}
+ return self._suite_list
+
+ def get_test_list(self):
+ self._generate_ci_tasks()
+ return {
+ "Awsy tests": {
+ "tp6": {},
+ "base": {},
+ "dmd": {},
+ "tp5": {},
+ }
+ }
+
+ def build_suite_section(self, title, content):
+ return self._build_section_with_header(
+ title.capitalize(), content, header_type="H4"
+ )
+
+ def build_test_description(self, title, test_description="", suite_name=""):
+ dropdown_suite_name = suite_name.replace(" ", "-")
+ result = f".. dropdown:: {title} ({test_description})\n"
+ result += f" :container: + anchor-id-{title}-{dropdown_suite_name}\n\n"
+
+ awsy_data = read_yaml(self._yaml_path)["suites"]["Awsy tests"]
+ if "owner" in awsy_data.keys():
+ result += f" **Owner**: {awsy_data['owner']}\n\n"
+ result += " * **Test Task**:\n"
+
+ # tp5 tests are represented by awsy-e10s test names
+ # while the others have their title in test names
+ search_tag = "awsy-e10s" if title == "tp5" else title
+ for platform in sorted(self._task_list.keys()):
+ result += f" * {platform}\n"
+ for test_dict in sorted(
+ self._task_list[platform], key=lambda d: d["test_name"]
+ ):
+ if search_tag in test_dict["test_name"]:
+ run_on_project = ": " + (
+ ", ".join(test_dict["run_on_projects"])
+ if test_dict["run_on_projects"]
+ else "None"
+ )
+ result += (
+ f" * {test_dict['test_name']}{run_on_project}\n"
+ )
+ result += "\n"
+
+ return [result]
+
+
+class StaticGatherer(FrameworkGatherer):
+ """
+ A noop gatherer for frameworks with static-only documentation.
+ """
+
+ pass
diff --git a/tools/lint/perfdocs/gatherer.py b/tools/lint/perfdocs/gatherer.py
new file mode 100644
index 0000000000..828c2f7f2b
--- /dev/null
+++ b/tools/lint/perfdocs/gatherer.py
@@ -0,0 +1,156 @@
+# 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 pathlib
+
+from perfdocs.framework_gatherers import (
+ AwsyGatherer,
+ MozperftestGatherer,
+ RaptorGatherer,
+ StaticGatherer,
+ TalosGatherer,
+)
+from perfdocs.logger import PerfDocLogger
+from perfdocs.utils import read_yaml
+
+logger = PerfDocLogger()
+
+# TODO: Implement decorator/searcher to find the classes.
+frameworks = {
+ "raptor": RaptorGatherer,
+ "mozperftest": MozperftestGatherer,
+ "talos": TalosGatherer,
+ "awsy": AwsyGatherer,
+}
+
+# List of file types allowed to be used as static files
+ALLOWED_STATIC_FILETYPES = ("rst", "png")
+
+
+class Gatherer(object):
+ """
+ Gatherer produces the tree of the perfdoc's entries found
+ and can obtain manifest-based test lists. Used by the Verifier.
+ """
+
+ def __init__(self, workspace_dir, taskgraph=None):
+ """
+ Initialzie the Gatherer.
+
+ :param str workspace_dir: Path to the gecko checkout.
+ """
+ self.workspace_dir = workspace_dir
+ self.taskgraph = taskgraph
+ self._perfdocs_tree = []
+ self._test_list = []
+ self.framework_gatherers = {}
+
+ @property
+ def perfdocs_tree(self):
+ """
+ Returns the perfdocs_tree, and computes it
+ if it doesn't exist.
+
+ :return dict: The perfdocs tree containing all
+ framework perfdoc entries. See `fetch_perfdocs_tree`
+ for information on the data structure.
+ """
+ if self._perfdocs_tree:
+ return self._perfdocs_tree
+ else:
+ self.fetch_perfdocs_tree()
+ return self._perfdocs_tree
+
+ def fetch_perfdocs_tree(self):
+ """
+ Creates the perfdocs tree with the following structure:
+ [
+ {
+ "path": Path to the perfdocs directory.
+ "yml": Name of the configuration YAML file.
+ "rst": Name of the RST file.
+ "static": Name of the static file.
+ }, ...
+ ]
+
+ This method doesn't return anything. The result can be found in
+ the perfdocs_tree attribute.
+ """
+ exclude_dir = [
+ str(pathlib.Path(self.workspace_dir, ".hg")),
+ str(pathlib.Path("tools", "lint")),
+ str(pathlib.Path("testing", "perfdocs")),
+ ]
+
+ for path in pathlib.Path(self.workspace_dir).rglob("perfdocs"):
+ if any(d in str(path.resolve()) for d in exclude_dir):
+ continue
+ files = [f for f in os.listdir(path)]
+ matched = {"path": str(path), "yml": "", "rst": "", "static": []}
+
+ for file in files:
+ # Add the yml/rst/static file to its key if re finds the searched file
+ if file == "config.yml" or file == "config.yaml":
+ matched["yml"] = file
+ elif file == "index.rst":
+ matched["rst"] = file
+ elif file.split(".")[-1] in ALLOWED_STATIC_FILETYPES:
+ matched["static"].append(file)
+
+ # Append to structdocs if all the searched files were found
+ if all(val for val in matched.values() if not type(val) == list):
+ self._perfdocs_tree.append(matched)
+
+ logger.log(
+ "Found {} perfdocs directories in {}".format(
+ len(self._perfdocs_tree),
+ [d["path"] for d in self._perfdocs_tree],
+ )
+ )
+
+ def get_test_list(self, sdt_entry):
+ """
+ Use a perfdocs_tree entry to find the test list for
+ the framework that was found.
+
+ :return: A framework info dictionary with fields: {
+ 'yml_path': Path to YAML,
+ 'yml_content': Content of YAML,
+ 'name': Name of framework,
+ 'test_list': Test list found for the framework
+ }
+ """
+
+ # If it was computed before, return it
+ yaml_path = pathlib.Path(sdt_entry["path"], sdt_entry["yml"])
+ for entry in self._test_list:
+ if entry["yml_path"] == yaml_path:
+ return entry
+
+ # Set up framework entry with meta data
+ yaml_content = read_yaml(yaml_path)
+ framework = {
+ "yml_content": yaml_content,
+ "yml_path": yaml_path,
+ "name": yaml_content["name"],
+ "test_list": {},
+ }
+
+ if yaml_content["static-only"]:
+ framework_gatherer_cls = StaticGatherer
+ else:
+ framework_gatherer_cls = frameworks[framework["name"]]
+
+ # Get and then store the frameworks tests
+ framework_gatherer = self.framework_gatherers[
+ framework["name"]
+ ] = framework_gatherer_cls(
+ framework["yml_path"], self.workspace_dir, self.taskgraph
+ )
+
+ if not yaml_content["static-only"]:
+ framework["test_list"] = framework_gatherer.get_test_list()
+
+ self._test_list.append(framework)
+ return framework
diff --git a/tools/lint/perfdocs/generator.py b/tools/lint/perfdocs/generator.py
new file mode 100644
index 0000000000..3f3a0acefa
--- /dev/null
+++ b/tools/lint/perfdocs/generator.py
@@ -0,0 +1,281 @@
+# 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 pathlib
+import re
+import shutil
+import tempfile
+
+from perfdocs.logger import PerfDocLogger
+from perfdocs.utils import (
+ ON_TRY,
+ are_dirs_equal,
+ get_changed_files,
+ read_file,
+ read_yaml,
+ save_file,
+)
+
+logger = PerfDocLogger()
+
+
+class Generator(object):
+ """
+ After each perfdocs directory was validated, the generator uses the templates
+ for each framework, fills them with the test descriptions in config and saves
+ the perfdocs in the form index.rst as index file and suite_name.rst for
+ each suite of tests in the framework.
+ """
+
+ def __init__(self, verifier, workspace, generate=False):
+ """
+ Initialize the Generator.
+
+ :param verifier: Verifier object. It should not be a fresh Verifier object,
+ but an initialized one with validate_tree() method already called
+ :param workspace: Path to the top-level checkout directory.
+ :param generate: Flag for generating the documentation
+ """
+ self._workspace = workspace
+ if not self._workspace:
+ raise Exception("PerfDocs Generator requires a workspace directory.")
+ # Template documents without added information reside here
+ self.templates_path = pathlib.Path(
+ self._workspace, "tools", "lint", "perfdocs", "templates"
+ )
+ self.perfdocs_path = pathlib.Path(
+ self._workspace, "testing", "perfdocs", "generated"
+ )
+
+ self._generate = generate
+ self._verifier = verifier
+ self._perfdocs_tree = self._verifier._gatherer.perfdocs_tree
+
+ def build_perfdocs_from_tree(self):
+ """
+ Builds up a document for each framework that was found.
+
+ :return dict: A dictionary containing a mapping from each framework
+ to the document that was built for it, i.e:
+ {
+ framework_name: framework_document,
+ ...
+ }
+ """
+
+ # Using the verified `perfdocs_tree`, build up the documentation.
+ frameworks_info = {}
+ for framework in self._perfdocs_tree:
+ yaml_content = read_yaml(pathlib.Path(framework["path"], framework["yml"]))
+ rst_content = read_file(
+ pathlib.Path(framework["path"], framework["rst"]), stringify=True
+ )
+
+ # Gather all tests and descriptions and format them into
+ # documentation content
+ documentation = []
+ suites = yaml_content["suites"]
+ for suite_name in sorted(suites.keys()):
+ suite_info = suites[suite_name]
+
+ # Add the suite section
+ documentation.extend(
+ self._verifier._gatherer.framework_gatherers[
+ yaml_content["name"]
+ ].build_suite_section(suite_name, suite_info["description"])
+ )
+
+ tests = suite_info.get("tests", {})
+ for test_name in sorted(tests.keys()):
+ gatherer = self._verifier._gatherer.framework_gatherers[
+ yaml_content["name"]
+ ]
+ test_description = gatherer.build_test_description(
+ test_name, tests[test_name], suite_name
+ )
+ documentation.extend(test_description)
+ documentation.append("")
+
+ # Insert documentation into `.rst` file
+ framework_rst = re.sub(
+ r"{documentation}", "\n".join(documentation), rst_content
+ )
+ frameworks_info[yaml_content["name"]] = {
+ "dynamic": framework_rst,
+ "static": [],
+ }
+
+ # For static `.rst` file
+ for static_file in framework["static"]:
+ if static_file.endswith("rst"):
+ frameworks_info[yaml_content["name"]]["static"].append(
+ {
+ "file": static_file,
+ "content": read_file(
+ pathlib.Path(framework["path"], static_file),
+ stringify=True,
+ ),
+ }
+ )
+ else:
+ frameworks_info[yaml_content["name"]]["static"].append(
+ {
+ "file": static_file,
+ "content": pathlib.Path(framework["path"], static_file),
+ }
+ )
+
+ return frameworks_info
+
+ def _create_temp_dir(self):
+ """
+ Create a temp directory as preparation of saving the documentation tree.
+ :return: str the location of perfdocs_tmpdir
+ """
+ # Build the directory that will contain the final result (a tmp dir
+ # that will be moved to the final location afterwards)
+ try:
+ tmpdir = pathlib.Path(tempfile.mkdtemp())
+ perfdocs_tmpdir = pathlib.Path(tmpdir, "generated")
+ perfdocs_tmpdir.mkdir(parents=True, exist_ok=True)
+ perfdocs_tmpdir.chmod(0o766)
+ except OSError as e:
+ logger.critical("Error creating temp file: {}".format(e))
+
+ if perfdocs_tmpdir.is_dir():
+ return perfdocs_tmpdir
+ return False
+
+ def _create_perfdocs(self):
+ """
+ Creates the perfdocs documentation.
+ :return: str path of the temp dir it is saved in
+ """
+ # All directories that are kept in the perfdocs tree are valid,
+ # so use it to build up the documentation.
+ framework_docs = self.build_perfdocs_from_tree()
+ perfdocs_tmpdir = self._create_temp_dir()
+
+ # Save the documentation files
+ frameworks = []
+ for framework_name in sorted(framework_docs.keys()):
+ frameworks.append(framework_name)
+ save_file(
+ framework_docs[framework_name]["dynamic"],
+ pathlib.Path(perfdocs_tmpdir, framework_name),
+ )
+
+ for static_name in framework_docs[framework_name]["static"]:
+ if static_name["file"].endswith(".rst"):
+ # XXX Replace this with a shutil.copy call (like below)
+ save_file(
+ static_name["content"],
+ pathlib.Path(
+ perfdocs_tmpdir, static_name["file"].split(".")[0]
+ ),
+ )
+ else:
+ shutil.copy(
+ static_name["content"],
+ pathlib.Path(perfdocs_tmpdir, static_name["file"]),
+ )
+
+ # Get the main page and add the framework links to it
+ mainpage = read_file(
+ pathlib.Path(self.templates_path, "index.rst"), stringify=True
+ )
+
+ fmt_frameworks = "\n".join([" * :doc:`%s`" % name for name in frameworks])
+ fmt_toctree = "\n".join([" %s" % name for name in frameworks])
+
+ fmt_mainpage = re.sub(r"{toctree_documentation}", fmt_toctree, mainpage)
+ fmt_mainpage = re.sub(r"{test_documentation}", fmt_frameworks, fmt_mainpage)
+
+ save_file(fmt_mainpage, pathlib.Path(perfdocs_tmpdir, "index"))
+
+ return perfdocs_tmpdir
+
+ def _save_perfdocs(self, perfdocs_tmpdir):
+ """
+ Copies the perfdocs tree after it was saved into the perfdocs_tmpdir
+ :param perfdocs_tmpdir: str location of the temp dir where the
+ perfdocs was saved
+ """
+ # Remove the old docs and copy the new version there without
+ # checking if they need to be regenerated.
+ logger.log("Regenerating perfdocs...")
+
+ if self.perfdocs_path.exists():
+ shutil.rmtree(str(self.perfdocs_path))
+
+ try:
+ saved = shutil.copytree(str(perfdocs_tmpdir), str(self.perfdocs_path))
+ if saved:
+ logger.log(
+ "Documentation saved to {}/".format(
+ re.sub(".*testing", "testing", str(self.perfdocs_path))
+ )
+ )
+ except Exception as e:
+ logger.critical(
+ "There was an error while saving the documentation: {}".format(e)
+ )
+
+ def generate_perfdocs(self):
+ """
+ Generate the performance documentation.
+
+ If `self._generate` is True, then the documentation will be regenerated
+ without any checks. Otherwise, if it is False, the new documentation will be
+ prepare and compare with the existing documentation to determine if
+ it should be regenerated.
+
+ :return bool: True/False - For True, if `self._generate` is True, then the
+ docs were regenerated. If `self._generate` is False, then True will mean
+ that the docs should be regenerated, and False means that they do not
+ need to be regenerated.
+ """
+
+ def get_possibly_changed_files():
+ """
+ Returns files that might have been modified
+ (used to output a linter warning for regeneration)
+ :return: list - files that might have been modified
+ """
+ # Returns files that might have been modified
+ # (used to output a linter warning for regeneration)
+ files = []
+ for entry in self._perfdocs_tree:
+ files.extend(
+ [
+ pathlib.Path(entry["path"], entry["yml"]),
+ pathlib.Path(entry["path"], entry["rst"]),
+ ]
+ )
+ return files
+
+ # Throw a warning if there's no need for generating
+ if not self.perfdocs_path.exists() and not self._generate:
+ # If they don't exist and we are not generating, then throw
+ # a linting error and exit.
+ logger.warning(
+ "PerfDocs need to be regenerated.", files=get_possibly_changed_files()
+ )
+ return True
+
+ perfdocs_tmpdir = self._create_perfdocs()
+ if self._generate:
+ self._save_perfdocs(perfdocs_tmpdir)
+ else:
+ # If we are not generating, then at least check if they
+ # should be regenerated by comparing the directories.
+ if not are_dirs_equal(perfdocs_tmpdir, self.perfdocs_path):
+ logger.warning(
+ "PerfDocs are outdated, run ./mach lint -l perfdocs --fix .` "
+ + "to update them. You can also apply the "
+ + f"{'perfdocs.diff' if ON_TRY else 'diff.txt'} patch file "
+ + f"{'produced from this reviewbot test ' if ON_TRY else ''}"
+ + "to fix the issue.",
+ files=get_changed_files(self._workspace),
+ restricted=False,
+ )
diff --git a/tools/lint/perfdocs/logger.py b/tools/lint/perfdocs/logger.py
new file mode 100644
index 0000000000..6f0a482d15
--- /dev/null
+++ b/tools/lint/perfdocs/logger.py
@@ -0,0 +1,81 @@
+# 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 pathlib
+
+
+class PerfDocLogger(object):
+ """
+ Logger for the PerfDoc tooling. Handles the warnings by outputting
+ them into through the StructuredLogger provided by lint.
+ """
+
+ TOP_DIR = ""
+ PATHS = []
+ LOGGER = None
+ FAILED = False
+
+ def __init__(self):
+ """Initializes the PerfDocLogger."""
+
+ # Set up class attributes for all logger instances
+ if not PerfDocLogger.LOGGER:
+ raise Exception(
+ "Missing linting LOGGER instance for PerfDocLogger initialization"
+ )
+ if not PerfDocLogger.PATHS:
+ raise Exception("Missing PATHS for PerfDocLogger initialization")
+ self.logger = PerfDocLogger.LOGGER
+
+ def log(self, msg):
+ """
+ Log an info message.
+
+ :param str msg: Message to log.
+ """
+ self.logger.info(msg)
+
+ def warning(self, msg, files, restricted=True):
+ """
+ Logs a validation warning message. The warning message is
+ used as the error message that is output in the reviewbot.
+
+ :param str msg: Message to log, it's also used as the error message
+ for the issue that is output by the reviewbot.
+ :param list/str files: The file(s) that this warning is about.
+ :param boolean restricted: If the param is False, the lint error can be used anywhere.
+ """
+ if type(files) != list:
+ files = [files]
+
+ # Add a reviewbot error for each file that is given
+ for file in files:
+ # Get a relative path (reviewbot can't handle absolute paths)
+ fpath = str(file).replace(str(PerfDocLogger.TOP_DIR), "")
+
+ # Filter out any issues that do not relate to the paths
+ # that are being linted
+ for path in PerfDocLogger.PATHS:
+ if restricted and str(path) not in str(file):
+ continue
+
+ # Output error entry
+ self.logger.lint_error(
+ message=msg,
+ lineno=0,
+ column=None,
+ path=str(pathlib.PurePosixPath(fpath)),
+ linter="perfdocs",
+ rule="Flawless performance docs.",
+ )
+
+ PerfDocLogger.FAILED = True
+ break
+
+ def critical(self, msg):
+ """
+ Log a critical message.
+
+ :param str msg: Message to log.
+ """
+ self.logger.critical(msg)
diff --git a/tools/lint/perfdocs/perfdocs.py b/tools/lint/perfdocs/perfdocs.py
new file mode 100644
index 0000000000..b41edb1979
--- /dev/null
+++ b/tools/lint/perfdocs/perfdocs.py
@@ -0,0 +1,95 @@
+# 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 pathlib
+
+
+def run_perfdocs(config, logger=None, paths=None, generate=True):
+ """
+ Build up performance testing documentation dynamically by combining
+ text data from YAML files that reside in `perfdoc` folders
+ across the `testing` directory. Each directory is expected to have
+ an `index.rst` file along with `config.yml` YAMLs defining what needs
+ to be added to the documentation.
+
+ The YAML must also define the name of the "framework" that should be
+ used in the main index.rst for the performance testing documentation.
+
+ The testing documentation list will be ordered alphabetically once
+ it's produced (to avoid unwanted shifts because of unordered dicts
+ and path searching).
+
+ Note that the suite name headings will be given the H4 (---) style so it
+ is suggested that you use H3 (===) style as the heading for your
+ test section. H5 will be used be used for individual tests within each
+ suite.
+
+ Usage for verification: "./mach lint -l perfdocs ."
+ Usage for generation: "./mach lint -l perfdocs --fix ."
+
+ For validation, see the Verifier class for a description of how
+ it works.
+
+ The run will fail if the valid result from validate_tree is not
+ False, implying some warning/problem was logged.
+
+ :param dict config: The configuration given by mozlint.
+ :param StructuredLogger logger: The StructuredLogger instance to be used to
+ output the linting warnings/errors.
+ :param list paths: The paths that are being tested. Used to filter
+ out errors from files outside of these paths.
+ :param bool generate: If true, the docs will be (re)generated.
+ """
+ from perfdocs.logger import PerfDocLogger
+
+ if not os.environ.get("WORKSPACE", None):
+ floc = pathlib.Path(__file__).absolute()
+ top_dir = pathlib.Path(str(floc).split("tools")[0]).resolve()
+ else:
+ top_dir = pathlib.Path(os.environ.get("WORKSPACE")).resolve()
+
+ PerfDocLogger.LOGGER = logger
+ PerfDocLogger.TOP_DIR = top_dir
+
+ # Convert all the paths to relative ones
+ target_dir = [pathlib.Path(path) for path in paths]
+ rel_paths = []
+ for path in target_dir:
+ try:
+ rel_paths.append(path.relative_to(top_dir))
+ except ValueError:
+ rel_paths.append(path)
+
+ PerfDocLogger.PATHS = rel_paths
+
+ for path in target_dir:
+ if not path.exists():
+ raise Exception("Cannot locate directory at %s" % str(path))
+
+ decision_task_id = os.environ.get("DECISION_TASK_ID", None)
+ if decision_task_id:
+ from taskgraph.util.taskcluster import get_artifact
+
+ task_graph = get_artifact(decision_task_id, "public/full-task-graph.json")
+ else:
+ from tryselect.tasks import generate_tasks
+
+ task_graph = generate_tasks(
+ params=None, full=True, disable_target_task_filter=True
+ ).tasks
+
+ # Late import because logger isn't defined until later
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ # Run the verifier first
+ verifier = Verifier(top_dir, task_graph)
+ verifier.validate_tree()
+
+ if not PerfDocLogger.FAILED:
+ # Even if the tree is valid, we need to check if the documentation
+ # needs to be regenerated, and if it does, we throw a linting error.
+ # `generate` dictates whether or not the documentation is generated.
+ generator = Generator(verifier, generate=generate, workspace=top_dir)
+ generator.generate_perfdocs()
diff --git a/tools/lint/perfdocs/templates/index.rst b/tools/lint/perfdocs/templates/index.rst
new file mode 100644
index 0000000000..5e684fef7d
--- /dev/null
+++ b/tools/lint/perfdocs/templates/index.rst
@@ -0,0 +1,86 @@
+###################
+Performance Testing
+###################
+
+.. toctree::
+ :maxdepth: 2
+ :hidden:
+ :glob:
+
+{toctree_documentation}
+
+Performance tests are designed to catch performance regressions before they reach our
+end users. At this time, there is no unified approach for these types of tests,
+but `mozperftest </testing/perfdocs/mozperftest.html>`_ aims to provide this in the future.
+
+For more detailed information about each test suite and project, see their documentation:
+
+{test_documentation}
+
+
+Here are the active PerfTest components/modules and their respective owners:
+
+ * AWFY (Are We Fast Yet) -
+ - Owner: Beatrice A.
+ - Description: A public dashboard comparing Firefox and Chrome performance metrics
+ * AWSY (Are We Slim Yet)
+ - Owner: Alexandru I.
+ - Description: Project that tracks memory usage across builds
+ * Raptor
+ - Owner: Sparky
+ - Co-owner: Kash
+ - Description: Test harness that uses Browsertime (based on webdriver) as the underlying engine to run performance tests
+ * CondProf (Conditioned profiles)
+ - Owner: Sparky
+ - Co-owner: Jmaher
+ - Description: Provides tooling to build, and obtain profiles that are preconditioned in some way.
+ * fxrecord
+ - Owner: Sparky
+ - Co-owners: Kash, Andrej
+ - Description: Tool for measuring startup performance for Firefox Desktop
+ * Infrastructure
+ - Owner: Sparky
+ - Co-owners: Kash, Andrej
+ - Description: All things involving: TaskCluster, Youtube Playback, Bitbar, Mobile Configs, etc...
+ * Mozperftest
+ - Owner: Sparky
+ - Co-owners: Kash, Andrej
+ - Description: Testing framework used to run performance tests
+ * Mozperftest Tools
+ - Owner: Sparky
+ - Co-owner: Alexandru I.
+ - Description: Various tools used by performance testing team
+ * Mozproxy
+ - Owner: Sparky
+ - Co-owner: Kash
+ - Description: An http proxy used to run tests against third-party websites in a reliable and reproducible way
+ * PerfCompare
+ - Owner: Kimberly
+ - Co-owner: Beatrice A.
+ - Description: Performance comparison tool used to compare performance of different commits within a repository
+ * PerfDocs
+ - Owner: Sparky
+ - Co-owner: Alexandru I.
+ - Description: Automatically generated performance test engineering documentation
+ * PerfHerder
+ - Owner: Beatrice A
+ - Co-owner: Andra A.
+ - Description: The framework used by the performance sheriffs to find performance regressions and for storing, and visualizing our performance data.
+ * Performance Sheriffing
+ - Owner: Alexandru I.
+ - Co-owner: Andra A.
+ - Description: Performance sheriffs are responsible for finding commits that cause performance regressions and getting fixes from devs or backing out the changes
+ * Talos
+ - Owner: Sparky
+ - Co-owner: Andrej
+ - Description: Testing framework used to run Firefox-specific performance tests
+ * WebPageTest
+ - Owner: Andrej
+ - Co-owner: Sparky
+ - Description: A test running in the mozperftest framework used as a third party performance benchmark
+
+You can additionally reach out to our team on
+the `#perftest <https://matrix.to/#/#perftest:mozilla.org>`_ channel on matrix
+
+For more information about the performance testing team,
+`visit the wiki page <https://wiki.mozilla.org/TestEngineering/Performance>`_.
diff --git a/tools/lint/perfdocs/utils.py b/tools/lint/perfdocs/utils.py
new file mode 100644
index 0000000000..3bf20d33cc
--- /dev/null
+++ b/tools/lint/perfdocs/utils.py
@@ -0,0 +1,156 @@
+# 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 difflib
+import filecmp
+import os
+import pathlib
+
+import yaml
+from mozversioncontrol import get_repository_object
+from perfdocs.logger import PerfDocLogger
+
+logger = PerfDocLogger()
+
+ON_TRY = "MOZ_AUTOMATION" in os.environ
+
+
+def save_file(file_content, path, extension="rst"):
+ """
+ Saves data into a file.
+
+ :param str path: Location and name of the file being saved
+ (without an extension).
+ :param str data: Content to write into the file.
+ :param str extension: Extension to save the file as.
+ """
+ new_file = pathlib.Path("{}.{}".format(str(path), extension))
+ with new_file.open("wb") as f:
+ f.write(file_content.encode("utf-8"))
+
+
+def read_file(path, stringify=False):
+ """
+ Opens a file and returns its contents.
+
+ :param str path: Path to the file.
+ :return list: List containing the lines in the file.
+ """
+ with path.open(encoding="utf-8") as f:
+ return f.read() if stringify else f.readlines()
+
+
+def read_yaml(yaml_path):
+ """
+ Opens a YAML file and returns the contents.
+
+ :param str yaml_path: Path to the YAML to open.
+ :return dict: Dictionary containing the YAML content.
+ """
+ contents = {}
+ try:
+ with yaml_path.open(encoding="utf-8") as f:
+ contents = yaml.safe_load(f)
+ except Exception as e:
+ logger.warning(
+ "Error opening file {}: {}".format(str(yaml_path), str(e)), str(yaml_path)
+ )
+
+ return contents
+
+
+def are_dirs_equal(dir_1, dir_2):
+ """
+ Compare two directories to see if they are equal. Files in each
+ directory are assumed to be equal if their names and contents
+ are equal.
+
+ :param dir_1: First directory path
+ :param dir_2: Second directory path
+ :return: True if the directory trees are the same and False otherwise.
+ """
+
+ dirs_cmp = filecmp.dircmp(str(dir_1.resolve()), str(dir_2.resolve()))
+ if dirs_cmp.left_only or dirs_cmp.right_only or dirs_cmp.funny_files:
+ logger.log("Some files are missing or are funny.")
+ for file in dirs_cmp.left_only:
+ logger.log(f"Missing in existing docs: {file}")
+ for file in dirs_cmp.right_only:
+ logger.log(f"Missing in new docs: {file}")
+ for file in dirs_cmp.funny_files:
+ logger.log(f"The following file is funny: {file}")
+ return False
+
+ _, mismatch, errors = filecmp.cmpfiles(
+ str(dir_1.resolve()), str(dir_2.resolve()), dirs_cmp.common_files, shallow=False
+ )
+
+ if mismatch or errors:
+ logger.log(f"Found mismatches: {mismatch}")
+
+ # The root for where to save the diff will be different based on
+ # whether we are running in CI or not
+ os_root = pathlib.Path.cwd().anchor
+ diff_root = pathlib.Path(os_root, "builds", "worker")
+ if not ON_TRY:
+ diff_root = pathlib.Path(PerfDocLogger.TOP_DIR, "artifacts")
+ diff_root.mkdir(parents=True, exist_ok=True)
+
+ diff_path = pathlib.Path(diff_root, "diff.txt")
+ with diff_path.open("w", encoding="utf-8") as diff_file:
+ for entry in mismatch:
+ logger.log(f"Mismatch found on {entry}")
+
+ with pathlib.Path(dir_1, entry).open(encoding="utf-8") as f:
+ newlines = f.readlines()
+ with pathlib.Path(dir_2, entry).open(encoding="utf-8") as f:
+ baselines = f.readlines()
+ for line in difflib.unified_diff(
+ baselines, newlines, fromfile="base", tofile="new"
+ ):
+ logger.log(line)
+
+ # Here we want to add to diff.txt in a patch format, we use
+ # the basedir to make the file names/paths relative and this is
+ # different in CI vs local runs.
+ basedir = pathlib.Path(
+ os_root, "builds", "worker", "checkouts", "gecko"
+ )
+ if not ON_TRY:
+ basedir = diff_root
+
+ relative_path = str(pathlib.Path(dir_2, entry)).split(str(basedir))[-1]
+ patch = difflib.unified_diff(
+ baselines, newlines, fromfile=relative_path, tofile=relative_path
+ )
+
+ write_header = True
+ for line in patch:
+ if write_header:
+ diff_file.write(
+ f"diff --git a/{relative_path} b/{relative_path}\n"
+ )
+ write_header = False
+ diff_file.write(line)
+
+ logger.log(f"Completed diff on {entry}")
+
+ logger.log(f"Saved diff to {diff_path}")
+
+ return False
+
+ for common_dir in dirs_cmp.common_dirs:
+ subdir_1 = pathlib.Path(dir_1, common_dir)
+ subdir_2 = pathlib.Path(dir_2, common_dir)
+ if not are_dirs_equal(subdir_1, subdir_2):
+ return False
+
+ return True
+
+
+def get_changed_files(top_dir):
+ """
+ Returns the changed files found with duplicates removed.
+ """
+ repo = get_repository_object(top_dir)
+ return list(set(repo.get_changed_files() + repo.get_outgoing_files()))
diff --git a/tools/lint/perfdocs/verifier.py b/tools/lint/perfdocs/verifier.py
new file mode 100644
index 0000000000..db6f849922
--- /dev/null
+++ b/tools/lint/perfdocs/verifier.py
@@ -0,0 +1,600 @@
+# 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 pathlib
+import re
+
+import jsonschema
+from perfdocs.gatherer import Gatherer
+from perfdocs.logger import PerfDocLogger
+from perfdocs.utils import read_file, read_yaml
+
+logger = PerfDocLogger()
+
+"""
+Schema for the config.yml file.
+Expecting a YAML file with a format such as this:
+
+name: raptor
+manifest: testing/raptor/raptor/raptor.ini
+static-only: False
+suites:
+ desktop:
+ description: "Desktop tests."
+ tests:
+ raptor-tp6: "Raptor TP6 tests."
+ mobile:
+ description: "Mobile tests"
+ benchmarks:
+ description: "Benchmark tests."
+ tests:
+ wasm: "All wasm tests."
+
+"""
+CONFIG_SCHEMA = {
+ "definitions": {
+ "metrics_schema": {
+ "metric_name": {
+ "type": "object",
+ "properties": {
+ "aliases": {"type": "array", "items": {"type": "string"}},
+ "description": {"type": "string"},
+ "matcher": {"type": "string"},
+ },
+ "required": ["description", "aliases"],
+ },
+ },
+ },
+ "type": "object",
+ "properties": {
+ "name": {"type": "string"},
+ "manifest": {"type": "string"},
+ "static-only": {"type": "boolean"},
+ "metrics": {"$ref": "#/definitions/metrics_schema"},
+ "suites": {
+ "type": "object",
+ "properties": {
+ "suite_name": {
+ "type": "object",
+ "properties": {
+ "tests": {
+ "type": "object",
+ "properties": {
+ "test_name": {"type": "string"},
+ "metrics": {"$ref": "#/definitions/metrics_schema"},
+ },
+ },
+ "description": {"type": "string"},
+ "owner": {"type": "string"},
+ "metrics": {"$ref": "#/definitions/metrics_schema"},
+ },
+ "required": ["description"],
+ }
+ },
+ },
+ },
+ "required": ["name", "manifest", "static-only", "suites"],
+}
+
+
+class Verifier(object):
+ """
+ Verifier is used for validating the perfdocs folders/tree. In the future,
+ the generator will make use of this class to obtain a validated set of
+ descriptions that can be used to build up a document.
+ """
+
+ def __init__(self, workspace_dir, taskgraph=None):
+ """
+ Initialize the Verifier.
+
+ :param str workspace_dir: Path to the top-level checkout directory.
+ """
+ self.workspace_dir = workspace_dir
+ self._gatherer = Gatherer(workspace_dir, taskgraph)
+ self._compiled_matchers = {}
+
+ def _is_yaml_test_match(
+ self, target_test_name, test_name, suite="", global_descriptions={}
+ ):
+ """Determine if a target name (from a YAML) matches with a test."""
+ tb = os.path.basename(target_test_name)
+ tb = re.sub("\..*", "", tb)
+ if test_name == tb:
+ # Found an exact match for the test_name
+ return True
+ if test_name in tb:
+ # Found a 'fuzzy' match for the test_name
+ # i.e. 'wasm' could exist for all raptor wasm tests
+ global_descriptions.setdefault(suite, []).append(test_name)
+ return True
+
+ def _validate_desc_yaml_direction(
+ self, suite, framework_info, yaml_content, global_descriptions
+ ):
+ """Validate the descriptions in the YAML.
+
+ This validation ensures that all tests defined in the YAML exist in the test
+ harness. Failures here suggest that there's a typo in the YAML or that
+ a test was removed.
+ """
+ ytests = yaml_content["suites"][suite]
+ global_descriptions[suite] = []
+ if not ytests.get("tests"):
+ # It's possible a suite entry has no tests
+ return True
+
+ # Suite found - now check if any tests in YAML
+ # definitions don't exist
+ ytests = ytests["tests"]
+ for test_name in ytests:
+ foundtest = False
+ for t in framework_info["test_list"][suite]:
+ if self._is_yaml_test_match(
+ t, test_name, suite=suite, global_descriptions=global_descriptions
+ ):
+ foundtest = True
+ break
+ if not foundtest:
+ logger.warning(
+ "Could not find an existing test for {} - bad test name?".format(
+ test_name
+ ),
+ framework_info["yml_path"],
+ )
+ return False
+
+ def _validate_desc_harness_direction(
+ self, suite, test_list, yaml_content, global_descriptions
+ ):
+ """Validate that the tests have a description in the YAML.
+
+ This stage of validation ensures that all the tests have some
+ form of description, or that global descriptions are available.
+ Failures here suggest a new test was added, or the config.yml
+ file was changed.
+ """
+ # If only a description is provided for the suite, assume
+ # that this is a suite-wide description and don't check for
+ # it's tests
+ stests = yaml_content["suites"][suite].get("tests", None)
+ if not stests:
+ return
+
+ tests_found = 0
+ missing_tests = []
+ test_to_manifest = {}
+ for test_name, test_info in test_list.items():
+ manifest_path = test_info.get("path", test_info.get("manifest", ""))
+ tb = os.path.basename(manifest_path)
+ tb = re.sub("\..*", "", tb)
+ if (
+ stests.get(tb, None) is not None
+ or stests.get(test_name, None) is not None
+ ):
+ # Test description exists, continue with the next test
+ tests_found += 1
+ continue
+ test_to_manifest[test_name] = manifest_path
+ missing_tests.append(test_name)
+
+ # Check if global test descriptions exist (i.e.
+ # ones that cover all of tp6) for the missing tests
+ new_mtests = []
+ for mt in missing_tests:
+ found = False
+ for test_name in global_descriptions[suite]:
+ # Global test exists for this missing test
+ if mt.startswith(test_name):
+ found = True
+ break
+ if test_name in mt:
+ found = True
+ break
+ if not found:
+ new_mtests.append(mt)
+
+ if len(new_mtests):
+ # Output an error for each manifest with a missing
+ # test description
+ for test_name in new_mtests:
+ logger.warning(
+ "Could not find a test description for {}".format(test_name),
+ test_to_manifest[test_name],
+ )
+
+ def _match_metrics(self, target_metric_name, target_metric_info, measured_metrics):
+ """Find all metrics that match the given information.
+
+ It either checks for the metric through a direct equality check, and if
+ a regex matcher was provided, we will use that afterwards.
+ """
+ verified_metrics = []
+
+ metric_names = target_metric_info["aliases"] + [target_metric_name]
+ for measured_metric in measured_metrics:
+ if measured_metric in metric_names:
+ verified_metrics.append(measured_metric)
+
+ if target_metric_info.get("matcher", ""):
+ # Compile the regex separately to capture issues in the regex
+ # compilation
+ matcher = self._compiled_matchers.get(target_metric_name, None)
+ if not matcher:
+ matcher = re.compile(target_metric_info.get("matcher"))
+ self._compiled_matchers[target_metric_name] = matcher
+
+ # Search the measured metrics
+ for measured_metric in measured_metrics:
+ if matcher.search(measured_metric):
+ verified_metrics.append(measured_metric)
+
+ return verified_metrics
+
+ def _validate_metrics_yaml_direction(
+ self, suite, framework_info, yaml_content, global_metrics
+ ):
+ """Validate the metric descriptions in the YAML.
+
+ This direction (`yaml_direction`) checks that the YAML definitions exist in
+ the test harness as real metrics. Failures here suggest that a metric
+ changed name, is missing an alias, is misnamed, duplicated, or was removed.
+ """
+ yaml_suite = yaml_content["suites"][suite]
+ suite_metrics = yaml_suite.get("metrics", {})
+
+ # Check to make sure all the metrics with given descriptions
+ # are actually being measured. Add the metric to the "verified" field in
+ # global_metrics to use it later for "global" metrics that can
+ # have their descriptions removed. Start from the test level.
+ for test_name, test_info in yaml_suite.get("tests", {}).items():
+ if not isinstance(test_info, dict):
+ continue
+ test_metrics_info = test_info.get("metrics", {})
+
+ # Find all tests that match with this name in case they measure
+ # different things
+ measured_metrics = []
+ for t in framework_info["test_list"][suite]:
+ if not self._is_yaml_test_match(t, test_name):
+ # Check to make sure we are checking against the right
+ # test. Skip the metric check if we can't find the test.
+ continue
+ measured_metrics.extend(
+ framework_info["test_list"][suite][t].get("metrics", [])
+ )
+
+ if len(measured_metrics) == 0:
+ continue
+
+ # Check if all the test metrics documented exist
+ for metric_name, metric_info in test_metrics_info.items():
+ verified_metrics = self._match_metrics(
+ metric_name, metric_info, measured_metrics
+ )
+ if len(verified_metrics) > 0:
+ global_metrics["yaml-verified"].extend(
+ [metric_name] + metric_info["aliases"]
+ )
+ global_metrics["verified"].extend(
+ [metric_name] + metric_info["aliases"] + verified_metrics
+ )
+ else:
+ logger.warning(
+ (
+ "Cannot find documented metric `{}` "
+ "being used in the specified test `{}`."
+ ).format(metric_name, test_name),
+ framework_info["yml_path"],
+ )
+
+ # Check the suite level now
+ for suite_metric_name, suite_metric_info in suite_metrics.items():
+ measured_metrics = []
+ for _, test_info in framework_info["test_list"][suite].items():
+ measured_metrics.extend(test_info.get("metrics", []))
+
+ verified_metrics = self._match_metrics(
+ suite_metric_name, suite_metric_info, measured_metrics
+ )
+ if len(verified_metrics) > 0:
+ global_metrics["yaml-verified"].extend(
+ [suite_metric_name] + suite_metric_info["aliases"]
+ )
+ global_metrics["verified"].extend(
+ [suite_metric_name]
+ + suite_metric_info["aliases"]
+ + verified_metrics
+ )
+ else:
+ logger.warning(
+ (
+ "Cannot find documented metric `{}` "
+ "being used in the specified suite `{}`."
+ ).format(suite_metric_name, suite),
+ framework_info["yml_path"],
+ )
+
+ # Finally check the global level (output failures later)
+ all_measured_metrics = []
+ for _, test_info in framework_info["test_list"][suite].items():
+ all_measured_metrics.extend(test_info.get("metrics", []))
+ for global_metric_name, global_metric_info in global_metrics["global"].items():
+ verified_metrics = self._match_metrics(
+ global_metric_name, global_metric_info, all_measured_metrics
+ )
+ if global_metric_info.get("verified", False):
+ # We already verified this global metric, but add any
+ # extra verified metrics here
+ global_metrics["verified"].extend(verified_metrics)
+ continue
+ if len(verified_metrics) > 0:
+ global_metric_info["verified"] = True
+ global_metrics["yaml-verified"].extend(
+ [global_metric_name] + global_metric_info["aliases"]
+ )
+ global_metrics["verified"].extend(
+ [global_metric_name]
+ + global_metric_info["aliases"]
+ + verified_metrics
+ )
+
+ def _validate_metrics_harness_direction(
+ self, suite, test_list, yaml_content, global_metrics
+ ):
+ """Validate that metrics in the harness are documented."""
+ # Gather all the metrics being measured
+ all_measured_metrics = {}
+ for test_name, test_info in test_list.items():
+ metrics = test_info.get("metrics", [])
+ for metric in metrics:
+ all_measured_metrics.setdefault(metric, []).append(test_name)
+
+ if len(all_measured_metrics) == 0:
+ # There are no metrics measured by this suite
+ return
+
+ for metric, tests in all_measured_metrics.items():
+ if metric not in global_metrics["verified"]:
+ # Log a warning in all files that have this metric
+ for test in tests:
+ logger.warning(
+ "Missing description for the metric `{}` in test `{}`".format(
+ metric, test
+ ),
+ test_list[test].get(
+ "path", test_list[test].get("manifest", "")
+ ),
+ )
+
+ def validate_descriptions(self, framework_info):
+ """
+ Cross-validate the tests found in the manifests and the YAML
+ test definitions. This function doesn't return a valid flag. Instead,
+ the StructDocLogger.VALIDATION_LOG is used to determine validity.
+
+ The validation proceeds as follows:
+ 1. Check that all tests/suites in the YAML exist in the manifests.
+ - At the same time, build a list of global descriptions which
+ define descriptions for groupings of tests.
+ 2. Check that all tests/suites found in the manifests exist in the YAML.
+ - For missing tests, check if a global description for them exists.
+
+ As the validation is completed, errors are output into the validation log
+ for any issues that are found.
+
+ The same is done for the metrics field expect it also has regex matching,
+ and the definitions cannot be duplicated in a single harness. We make use
+ of two `*verified` fields to simplify the two stages/directions, and checking
+ for any duplication.
+
+ :param dict framework_info: Contains information about the framework. See
+ `Gatherer.get_test_list` for information about its structure.
+ """
+ yaml_content = framework_info["yml_content"]
+
+ # Check for any bad test/suite names in the yaml config file
+ # TODO: Combine global settings into a single dictionary
+ global_descriptions = {}
+ global_metrics = {
+ "global": yaml_content.get("metrics", {}),
+ "verified": [],
+ "yaml-verified": [],
+ }
+ for suite, ytests in yaml_content["suites"].items():
+ # Find the suite, then check against the tests within it
+ if framework_info["test_list"].get(suite, None) is None:
+ logger.warning(
+ "Could not find an existing suite for {} - bad suite name?".format(
+ suite
+ ),
+ framework_info["yml_path"],
+ )
+ continue
+
+ # Validate descriptions
+ self._validate_desc_yaml_direction(
+ suite, framework_info, yaml_content, global_descriptions
+ )
+
+ # Validate metrics
+ self._validate_metrics_yaml_direction(
+ suite, framework_info, yaml_content, global_metrics
+ )
+
+ # The suite and test levels were properly checked, but we can only
+ # check the global level after all suites were checked. If the metric
+ # isn't in the verified
+ for global_metric_name, _ in global_metrics["global"].items():
+ if global_metric_name not in global_metrics["verified"]:
+ logger.warning(
+ (
+ "Cannot find documented metric `{}` "
+ "being used in the specified harness `{}`."
+ ).format(global_metric_name, yaml_content["name"]),
+ framework_info["yml_path"],
+ )
+
+ # Check for duplicate metrics/aliases in the verified metrics
+ unique_metrics = set()
+ warned = set()
+ for metric in global_metrics["yaml-verified"]:
+ if (
+ metric in unique_metrics or unique_metrics.add(metric)
+ ) and metric not in warned:
+ logger.warning(
+ "Duplicate definitions found for `{}`.".format(metric),
+ framework_info["yml_path"],
+ )
+ warned.add(metric)
+
+ # Check for duplicate metrics in the global level
+ unique_metrics = set()
+ warned = set()
+ for metric, metric_info in global_metrics["global"].items():
+ if (
+ metric in unique_metrics or unique_metrics.add(metric)
+ ) and metric not in warned:
+ logger.warning(
+ "Duplicate definitions found for `{}`.".format(metric),
+ framework_info["yml_path"],
+ )
+ for alias in metric_info.get("aliases", []):
+ unique_metrics.add(alias)
+ warned.add(alias)
+ warned.add(metric)
+
+ # Check for any missing tests/suites
+ for suite, test_list in framework_info["test_list"].items():
+ if not yaml_content["suites"].get(suite):
+ # Description doesn't exist for the suite
+ logger.warning(
+ "Missing suite description for {}".format(suite),
+ yaml_content["manifest"],
+ )
+ continue
+
+ self._validate_desc_harness_direction(
+ suite, test_list, yaml_content, global_descriptions
+ )
+
+ self._validate_metrics_harness_direction(
+ suite, test_list, yaml_content, global_metrics
+ )
+
+ def validate_yaml(self, yaml_path):
+ """
+ Validate that the YAML file has all the fields that are
+ required and parse the descriptions into strings in case
+ some are give as relative file paths.
+
+ :param str yaml_path: Path to the YAML to validate.
+ :return bool: True/False => Passed/Failed Validation
+ """
+
+ def _get_description(desc):
+ """
+ Recompute the description in case it's a file.
+ """
+ desc_path = pathlib.Path(self.workspace_dir, desc)
+
+ try:
+ if desc_path.exists() and desc_path.is_file():
+ with open(desc_path, "r") as f:
+ desc = f.readlines()
+ except OSError:
+ pass
+
+ return desc
+
+ def _parse_descriptions(content):
+ for suite, sinfo in content.items():
+ desc = sinfo["description"]
+ sinfo["description"] = _get_description(desc)
+
+ # It's possible that the suite has no tests and
+ # only a description. If they exist, then parse them.
+ if "tests" in sinfo:
+ for test, desc in sinfo["tests"].items():
+ sinfo["tests"][test] = _get_description(desc)
+
+ valid = False
+ yaml_content = read_yaml(yaml_path)
+
+ try:
+ jsonschema.validate(instance=yaml_content, schema=CONFIG_SCHEMA)
+ _parse_descriptions(yaml_content["suites"])
+ valid = True
+ except Exception as e:
+ logger.warning("YAML ValidationError: {}".format(str(e)), yaml_path)
+
+ return valid
+
+ def validate_rst_content(self, rst_path):
+ """
+ Validate that the index file given has a {documentation} entry
+ so that the documentation can be inserted there.
+
+ :param str rst_path: Path to the RST file.
+ :return bool: True/False => Passed/Failed Validation
+ """
+ rst_content = read_file(rst_path)
+
+ # Check for a {documentation} entry in some line,
+ # if we can't find one, then the validation fails.
+ valid = False
+ docs_match = re.compile(".*{documentation}.*")
+ for line in rst_content:
+ if docs_match.search(line):
+ valid = True
+ break
+ if not valid:
+ logger.warning(
+ "Cannot find a '{documentation}' entry in the given index file",
+ rst_path,
+ )
+
+ return valid
+
+ def _check_framework_descriptions(self, item):
+ """
+ Helper method for validating descriptions
+ """
+ framework_info = self._gatherer.get_test_list(item)
+ self.validate_descriptions(framework_info)
+
+ def validate_tree(self):
+ """
+ Validate the `perfdocs` directory that was found.
+ Returns True if it is good, false otherwise.
+
+ :return bool: True/False => Passed/Failed Validation
+ """
+ found_good = 0
+
+ # For each framework, check their files and validate descriptions
+ for matched in self._gatherer.perfdocs_tree:
+ # Get the paths to the YAML and RST for this framework
+ matched_yml = pathlib.Path(matched["path"], matched["yml"])
+ matched_rst = pathlib.Path(matched["path"], matched["rst"])
+
+ _valid_files = {
+ "yml": self.validate_yaml(matched_yml),
+ "rst": True,
+ }
+ if not read_yaml(matched_yml)["static-only"]:
+ _valid_files["rst"] = self.validate_rst_content(matched_rst)
+
+ # Log independently the errors found for the matched files
+ for file_format, valid in _valid_files.items():
+ if not valid:
+ logger.log("File validation error: {}".format(file_format))
+ if not all(_valid_files.values()):
+ continue
+ found_good += 1
+
+ self._check_framework_descriptions(matched)
+
+ if not found_good:
+ raise Exception("No valid perfdocs directories found")
diff --git a/tools/lint/pylint.yml b/tools/lint/pylint.yml
new file mode 100644
index 0000000000..012629e53b
--- /dev/null
+++ b/tools/lint/pylint.yml
@@ -0,0 +1,24 @@
+---
+pylint:
+ description: A second Python linter
+ include:
+ - configure.py
+ - client.py
+ - security/
+ - accessible/
+ - docs/
+ - dom/base/
+ - dom/websocket/
+ - mozglue/
+ - toolkit/components/telemetry/
+ exclude:
+ - dom/bindings/Codegen.py
+ - security/manager/ssl/tests/unit/test_content_signing/pysign.py
+ - security/ct/tests/gtest/createSTHTestData.py
+ extensions: ['py']
+ support-files:
+ - '**/.pylint'
+ - 'tools/lint/python/pylint*'
+ type: external
+ payload: python.pylint:lint
+ setup: python.pylint:setup
diff --git a/tools/lint/python/__init__.py b/tools/lint/python/__init__.py
new file mode 100644
index 0000000000..c580d191c1
--- /dev/null
+++ b/tools/lint/python/__init__.py
@@ -0,0 +1,3 @@
+# 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/.
diff --git a/tools/lint/python/black.py b/tools/lint/python/black.py
new file mode 100644
index 0000000000..8c44a56951
--- /dev/null
+++ b/tools/lint/python/black.py
@@ -0,0 +1,179 @@
+# 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 platform
+import re
+import signal
+import subprocess
+import sys
+
+import mozpack.path as mozpath
+from mozfile import which
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+from mozprocess import ProcessHandler
+
+here = os.path.abspath(os.path.dirname(__file__))
+BLACK_REQUIREMENTS_PATH = os.path.join(here, "black_requirements.txt")
+
+BLACK_INSTALL_ERROR = """
+Unable to install correct version of black
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ BLACK_REQUIREMENTS_PATH
+)
+
+
+def default_bindir():
+ # We use sys.prefix to find executables as that gets modified with
+ # virtualenv's activate_this.py, whereas sys.executable doesn't.
+ if platform.system() == "Windows":
+ return os.path.join(sys.prefix, "Scripts")
+ else:
+ return os.path.join(sys.prefix, "bin")
+
+
+def get_black_version(binary):
+ """
+ Returns found binary's version
+ """
+ try:
+ output = subprocess.check_output(
+ [binary, "--version"],
+ stderr=subprocess.STDOUT,
+ universal_newlines=True,
+ )
+ except subprocess.CalledProcessError as e:
+ output = e.output
+ try:
+ # Accept `black.EXE, version ...` on Windows.
+ # for old version of black, the output is
+ # black, version 21.4b2
+ # From black 21.11b1, the output is like
+ # black, 21.11b1 (compiled: no)
+ return re.match(r"black.*,( version)? (\S+)", output)[2]
+ except TypeError as e:
+ print("Could not parse the version '{}'".format(output))
+ print("Error: {}".format(e))
+
+
+def parse_issues(config, output, paths, *, log):
+ would_reformat = re.compile("^would reformat (.*)$", re.I)
+ reformatted = re.compile("^reformatted (.*)$", re.I)
+ cannot_reformat = re.compile("^error: cannot format (.*?): (.*)$", re.I)
+ results = []
+ for line in output:
+ line = line.decode("utf-8")
+ if line.startswith("All done!") or line.startswith("Oh no!"):
+ break
+
+ match = would_reformat.match(line)
+ if match:
+ res = {"path": match.group(1), "level": "error"}
+ results.append(result.from_config(config, **res))
+ continue
+
+ match = reformatted.match(line)
+ if match:
+ res = {"path": match.group(1), "level": "warning", "message": "reformatted"}
+ results.append(result.from_config(config, **res))
+ continue
+
+ match = cannot_reformat.match(line)
+ if match:
+ res = {"path": match.group(1), "level": "error", "message": match.group(2)}
+ results.append(result.from_config(config, **res))
+ continue
+
+ log.debug("Unhandled line", line)
+ return results
+
+
+class BlackProcess(ProcessHandler):
+ def __init__(self, config, *args, **kwargs):
+ self.config = config
+ kwargs["stream"] = False
+ ProcessHandler.__init__(self, *args, **kwargs)
+
+ def run(self, *args, **kwargs):
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ ProcessHandler.run(self, *args, **kwargs)
+ signal.signal(signal.SIGINT, orig)
+
+
+def run_process(config, cmd):
+ proc = BlackProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+ return proc.output
+
+
+def setup(root, **lintargs):
+ log = lintargs["log"]
+ virtualenv_bin_path = lintargs.get("virtualenv_bin_path")
+ # Using `which` searches multiple directories and handles `.exe` on Windows.
+ binary = which("black", path=(virtualenv_bin_path, default_bindir()))
+
+ if binary and os.path.exists(binary):
+ binary = mozpath.normsep(binary)
+ log.debug("Looking for black at {}".format(binary))
+ version = get_black_version(binary)
+ versions = [
+ line.split()[0].strip()
+ for line in open(BLACK_REQUIREMENTS_PATH).readlines()
+ if line.startswith("black==")
+ ]
+ if ["black=={}".format(version)] == versions:
+ log.debug("Black is present with expected version {}".format(version))
+ return 0
+ else:
+ log.debug("Black is present but unexpected version {}".format(version))
+
+ log.debug("Black needs to be installed or updated")
+ virtualenv_manager = lintargs["virtualenv_manager"]
+ try:
+ virtualenv_manager.install_pip_requirements(BLACK_REQUIREMENTS_PATH, quiet=True)
+ except subprocess.CalledProcessError:
+ print(BLACK_INSTALL_ERROR)
+ return 1
+
+
+def run_black(config, paths, fix=None, *, log, virtualenv_bin_path):
+ fixed = 0
+ binary = os.path.join(virtualenv_bin_path or default_bindir(), "black")
+
+ log.debug("Black version {}".format(get_black_version(binary)))
+
+ cmd_args = [binary]
+ if not fix:
+ cmd_args.append("--check")
+
+ base_command = cmd_args + paths
+ log.debug("Command: {}".format(" ".join(base_command)))
+ output = parse_issues(config, run_process(config, base_command), paths, log=log)
+
+ # black returns an issue for fixed files as well
+ for eachIssue in output:
+ if eachIssue.message == "reformatted":
+ fixed += 1
+
+ return {"results": output, "fixed": fixed}
+
+
+def lint(paths, config, fix=None, **lintargs):
+ files = list(expand_exclusions(paths, config, lintargs["root"]))
+
+ return run_black(
+ config,
+ files,
+ fix=fix,
+ log=lintargs["log"],
+ virtualenv_bin_path=lintargs.get("virtualenv_bin_path"),
+ )
diff --git a/tools/lint/python/black_requirements.in b/tools/lint/python/black_requirements.in
new file mode 100644
index 0000000000..e5efa47492
--- /dev/null
+++ b/tools/lint/python/black_requirements.in
@@ -0,0 +1,4 @@
+black==21.11b1
+typing-extensions==3.10.0.2
+dataclasses==0.6
+
diff --git a/tools/lint/python/black_requirements.txt b/tools/lint/python/black_requirements.txt
new file mode 100644
index 0000000000..944e5a83ec
--- /dev/null
+++ b/tools/lint/python/black_requirements.txt
@@ -0,0 +1,118 @@
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+# pip-compile --generate-hashes --output-file=tools/lint/python/black_requirements.txt tools/lint/python/black_requirements.in
+#
+black==21.11b1 \
+ --hash=sha256:802c6c30b637b28645b7fde282ed2569c0cd777dbe493a41b6a03c1d903f99ac \
+ --hash=sha256:a042adbb18b3262faad5aff4e834ff186bb893f95ba3a8013f09de1e5569def2
+ # via -r tools/lint/python/black_requirements.in
+click==8.0.3 \
+ --hash=sha256:353f466495adaeb40b6b5f592f9f91cb22372351c84caeb068132442a4518ef3 \
+ --hash=sha256:410e932b050f5eed773c4cda94de75971c89cdb3155a72a0831139a79e5ecb5b
+ # via black
+dataclasses==0.6 \
+ --hash=sha256:454a69d788c7fda44efd71e259be79577822f5e3f53f029a22d08004e951dc9f \
+ --hash=sha256:6988bd2b895eef432d562370bb707d540f32f7360ab13da45340101bc2307d84
+ # via -r tools/lint/python/black_requirements.in
+mypy-extensions==0.4.3 \
+ --hash=sha256:090fedd75945a69ae91ce1303b5824f428daf5a028d2f6ab8a299250a846f15d \
+ --hash=sha256:2d82818f5bb3e369420cb3c4060a7970edba416647068eb4c5343488a6c604a8
+ # via black
+pathspec==0.9.0 \
+ --hash=sha256:7d15c4ddb0b5c802d161efc417ec1a2558ea2653c2e8ad9c19098201dc1c993a \
+ --hash=sha256:e564499435a2673d586f6b2130bb5b95f04a3ba06f81b8f895b651a3c76aabb1
+ # via black
+platformdirs==2.4.0 \
+ --hash=sha256:367a5e80b3d04d2428ffa76d33f124cf11e8fff2acdaa9b43d545f5c7d661ef2 \
+ --hash=sha256:8868bbe3c3c80d42f20156f22e7131d2fb321f5bc86a2a345375c6481a67021d
+ # via black
+regex==2021.11.10 \
+ --hash=sha256:05b7d6d7e64efe309972adab77fc2af8907bb93217ec60aa9fe12a0dad35874f \
+ --hash=sha256:0617383e2fe465732af4509e61648b77cbe3aee68b6ac8c0b6fe934db90be5cc \
+ --hash=sha256:07856afef5ffcc052e7eccf3213317fbb94e4a5cd8177a2caa69c980657b3cb4 \
+ --hash=sha256:162abfd74e88001d20cb73ceaffbfe601469923e875caf9118333b1a4aaafdc4 \
+ --hash=sha256:2207ae4f64ad3af399e2d30dde66f0b36ae5c3129b52885f1bffc2f05ec505c8 \
+ --hash=sha256:30ab804ea73972049b7a2a5c62d97687d69b5a60a67adca07eb73a0ddbc9e29f \
+ --hash=sha256:3b5df18db1fccd66de15aa59c41e4f853b5df7550723d26aa6cb7f40e5d9da5a \
+ --hash=sha256:3c5fb32cc6077abad3bbf0323067636d93307c9fa93e072771cf9a64d1c0f3ef \
+ --hash=sha256:416c5f1a188c91e3eb41e9c8787288e707f7d2ebe66e0a6563af280d9b68478f \
+ --hash=sha256:432bd15d40ed835a51617521d60d0125867f7b88acf653e4ed994a1f8e4995dc \
+ --hash=sha256:4aaa4e0705ef2b73dd8e36eeb4c868f80f8393f5f4d855e94025ce7ad8525f50 \
+ --hash=sha256:537ca6a3586931b16a85ac38c08cc48f10fc870a5b25e51794c74df843e9966d \
+ --hash=sha256:53db2c6be8a2710b359bfd3d3aa17ba38f8aa72a82309a12ae99d3c0c3dcd74d \
+ --hash=sha256:5537f71b6d646f7f5f340562ec4c77b6e1c915f8baae822ea0b7e46c1f09b733 \
+ --hash=sha256:6650f16365f1924d6014d2ea770bde8555b4a39dc9576abb95e3cd1ff0263b36 \
+ --hash=sha256:666abff54e474d28ff42756d94544cdfd42e2ee97065857413b72e8a2d6a6345 \
+ --hash=sha256:68a067c11463de2a37157930d8b153005085e42bcb7ad9ca562d77ba7d1404e0 \
+ --hash=sha256:780b48456a0f0ba4d390e8b5f7c661fdd218934388cde1a974010a965e200e12 \
+ --hash=sha256:788aef3549f1924d5c38263104dae7395bf020a42776d5ec5ea2b0d3d85d6646 \
+ --hash=sha256:7ee1227cf08b6716c85504aebc49ac827eb88fcc6e51564f010f11a406c0a667 \
+ --hash=sha256:7f301b11b9d214f83ddaf689181051e7f48905568b0c7017c04c06dfd065e244 \
+ --hash=sha256:83ee89483672b11f8952b158640d0c0ff02dc43d9cb1b70c1564b49abe92ce29 \
+ --hash=sha256:85bfa6a5413be0ee6c5c4a663668a2cad2cbecdee367630d097d7823041bdeec \
+ --hash=sha256:9345b6f7ee578bad8e475129ed40123d265464c4cfead6c261fd60fc9de00bcf \
+ --hash=sha256:93a5051fcf5fad72de73b96f07d30bc29665697fb8ecdfbc474f3452c78adcf4 \
+ --hash=sha256:962b9a917dd7ceacbe5cd424556914cb0d636001e393b43dc886ba31d2a1e449 \
+ --hash=sha256:98ba568e8ae26beb726aeea2273053c717641933836568c2a0278a84987b2a1a \
+ --hash=sha256:a3feefd5e95871872673b08636f96b61ebef62971eab044f5124fb4dea39919d \
+ --hash=sha256:b43c2b8a330a490daaef5a47ab114935002b13b3f9dc5da56d5322ff218eeadb \
+ --hash=sha256:b483c9d00a565633c87abd0aaf27eb5016de23fed952e054ecc19ce32f6a9e7e \
+ --hash=sha256:ba05430e819e58544e840a68b03b28b6d328aff2e41579037e8bab7653b37d83 \
+ --hash=sha256:ca5f18a75e1256ce07494e245cdb146f5a9267d3c702ebf9b65c7f8bd843431e \
+ --hash=sha256:d5ca078bb666c4a9d1287a379fe617a6dccd18c3e8a7e6c7e1eb8974330c626a \
+ --hash=sha256:da1a90c1ddb7531b1d5ff1e171b4ee61f6345119be7351104b67ff413843fe94 \
+ --hash=sha256:dba70f30fd81f8ce6d32ddeef37d91c8948e5d5a4c63242d16a2b2df8143aafc \
+ --hash=sha256:dd33eb9bdcfbabab3459c9ee651d94c842bc8a05fabc95edf4ee0c15a072495e \
+ --hash=sha256:e0538c43565ee6e703d3a7c3bdfe4037a5209250e8502c98f20fea6f5fdf2965 \
+ --hash=sha256:e1f54b9b4b6c53369f40028d2dd07a8c374583417ee6ec0ea304e710a20f80a0 \
+ --hash=sha256:e32d2a2b02ccbef10145df9135751abea1f9f076e67a4e261b05f24b94219e36 \
+ --hash=sha256:e71255ba42567d34a13c03968736c5d39bb4a97ce98188fafb27ce981115beec \
+ --hash=sha256:ed2e07c6a26ed4bea91b897ee2b0835c21716d9a469a96c3e878dc5f8c55bb23 \
+ --hash=sha256:eef2afb0fd1747f33f1ee3e209bce1ed582d1896b240ccc5e2697e3275f037c7 \
+ --hash=sha256:f23222527b307970e383433daec128d769ff778d9b29343fb3496472dc20dabe \
+ --hash=sha256:f341ee2df0999bfdf7a95e448075effe0db212a59387de1a70690e4acb03d4c6 \
+ --hash=sha256:f7f325be2804246a75a4f45c72d4ce80d2443ab815063cdf70ee8fb2ca59ee1b \
+ --hash=sha256:f8af619e3be812a2059b212064ea7a640aff0568d972cd1b9e920837469eb3cb \
+ --hash=sha256:fa8c626d6441e2d04b6ee703ef2d1e17608ad44c7cb75258c09dd42bacdfc64b \
+ --hash=sha256:fbb9dc00e39f3e6c0ef48edee202f9520dafb233e8b51b06b8428cfcb92abd30 \
+ --hash=sha256:fff55f3ce50a3ff63ec8e2a8d3dd924f1941b250b0aac3d3d42b687eeff07a8e
+ # via black
+tomli==1.2.2 \
+ --hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee \
+ --hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade
+ # via black
+typed-ast==1.5.2 \
+ --hash=sha256:0eb77764ea470f14fcbb89d51bc6bbf5e7623446ac4ed06cbd9ca9495b62e36e \
+ --hash=sha256:1098df9a0592dd4c8c0ccfc2e98931278a6c6c53cb3a3e2cf7e9ee3b06153344 \
+ --hash=sha256:183b183b7771a508395d2cbffd6db67d6ad52958a5fdc99f450d954003900266 \
+ --hash=sha256:18fe320f354d6f9ad3147859b6e16649a0781425268c4dde596093177660e71a \
+ --hash=sha256:26a432dc219c6b6f38be20a958cbe1abffcc5492821d7e27f08606ef99e0dffd \
+ --hash=sha256:294a6903a4d087db805a7656989f613371915fc45c8cc0ddc5c5a0a8ad9bea4d \
+ --hash=sha256:31d8c6b2df19a777bc8826770b872a45a1f30cfefcfd729491baa5237faae837 \
+ --hash=sha256:33b4a19ddc9fc551ebabca9765d54d04600c4a50eda13893dadf67ed81d9a098 \
+ --hash=sha256:42c47c3b43fe3a39ddf8de1d40dbbfca60ac8530a36c9b198ea5b9efac75c09e \
+ --hash=sha256:525a2d4088e70a9f75b08b3f87a51acc9cde640e19cc523c7e41aa355564ae27 \
+ --hash=sha256:58ae097a325e9bb7a684572d20eb3e1809802c5c9ec7108e85da1eb6c1a3331b \
+ --hash=sha256:676d051b1da67a852c0447621fdd11c4e104827417bf216092ec3e286f7da596 \
+ --hash=sha256:74cac86cc586db8dfda0ce65d8bcd2bf17b58668dfcc3652762f3ef0e6677e76 \
+ --hash=sha256:8c08d6625bb258179b6e512f55ad20f9dfef019bbfbe3095247401e053a3ea30 \
+ --hash=sha256:90904d889ab8e81a956f2c0935a523cc4e077c7847a836abee832f868d5c26a4 \
+ --hash=sha256:963a0ccc9a4188524e6e6d39b12c9ca24cc2d45a71cfdd04a26d883c922b4b78 \
+ --hash=sha256:bbebc31bf11762b63bf61aaae232becb41c5bf6b3461b80a4df7e791fabb3aca \
+ --hash=sha256:bc2542e83ac8399752bc16e0b35e038bdb659ba237f4222616b4e83fb9654985 \
+ --hash=sha256:c29dd9a3a9d259c9fa19d19738d021632d673f6ed9b35a739f48e5f807f264fb \
+ --hash=sha256:c7407cfcad702f0b6c0e0f3e7ab876cd1d2c13b14ce770e412c0c4b9728a0f88 \
+ --hash=sha256:da0a98d458010bf4fe535f2d1e367a2e2060e105978873c04c04212fb20543f7 \
+ --hash=sha256:df05aa5b241e2e8045f5f4367a9f6187b09c4cdf8578bb219861c4e27c443db5 \
+ --hash=sha256:f290617f74a610849bd8f5514e34ae3d09eafd521dceaa6cf68b3f4414266d4e \
+ --hash=sha256:f30ddd110634c2d7534b2d4e0e22967e88366b0d356b24de87419cc4410c41b7
+ # via black
+typing-extensions==3.10.0.2 \
+ --hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
+ --hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \
+ --hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34
+ # via
+ # -r tools/lint/python/black_requirements.in
+ # black
diff --git a/tools/lint/python/flake8.py b/tools/lint/python/flake8.py
new file mode 100644
index 0000000000..88fec87822
--- /dev/null
+++ b/tools/lint/python/flake8.py
@@ -0,0 +1,215 @@
+# 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 json
+import os
+import platform
+import subprocess
+import sys
+
+import mozfile
+import mozpack.path as mozpath
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+here = os.path.abspath(os.path.dirname(__file__))
+FLAKE8_REQUIREMENTS_PATH = os.path.join(here, "flake8_requirements.txt")
+
+FLAKE8_NOT_FOUND = """
+Could not find flake8! Install flake8 and try again.
+
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ FLAKE8_REQUIREMENTS_PATH
+)
+
+
+FLAKE8_INSTALL_ERROR = """
+Unable to install correct version of flake8
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ FLAKE8_REQUIREMENTS_PATH
+)
+
+LINE_OFFSETS = {
+ # continuation line under-indented for hanging indent
+ "E121": (-1, 2),
+ # continuation line missing indentation or outdented
+ "E122": (-1, 2),
+ # continuation line over-indented for hanging indent
+ "E126": (-1, 2),
+ # continuation line over-indented for visual indent
+ "E127": (-1, 2),
+ # continuation line under-indented for visual indent
+ "E128": (-1, 2),
+ # continuation line unaligned for hanging indend
+ "E131": (-1, 2),
+ # expected 1 blank line, found 0
+ "E301": (-1, 2),
+ # expected 2 blank lines, found 1
+ "E302": (-2, 3),
+}
+"""Maps a flake8 error to a lineoffset tuple.
+
+The offset is of the form (lineno_offset, num_lines) and is passed
+to the lineoffset property of an `Issue`.
+"""
+
+
+def default_bindir():
+ # We use sys.prefix to find executables as that gets modified with
+ # virtualenv's activate_this.py, whereas sys.executable doesn't.
+ if platform.system() == "Windows":
+ return os.path.join(sys.prefix, "Scripts")
+ else:
+ return os.path.join(sys.prefix, "bin")
+
+
+class NothingToLint(Exception):
+ """Exception used to bail out of flake8's internals if all the specified
+ files were excluded.
+ """
+
+
+def setup(root, **lintargs):
+ virtualenv_manager = lintargs["virtualenv_manager"]
+ try:
+ virtualenv_manager.install_pip_requirements(
+ FLAKE8_REQUIREMENTS_PATH, quiet=True
+ )
+ except subprocess.CalledProcessError:
+ print(FLAKE8_INSTALL_ERROR)
+ return 1
+
+
+def lint(paths, config, **lintargs):
+
+ root = lintargs["root"]
+ virtualenv_bin_path = lintargs.get("virtualenv_bin_path")
+ config_path = os.path.join(root, ".flake8")
+
+ results = run(paths, config, **lintargs)
+ fixed = 0
+
+ if lintargs.get("fix"):
+ # fix and run again to count remaining issues
+ fixed = len(results)
+ fix_cmd = [
+ os.path.join(virtualenv_bin_path or default_bindir(), "autopep8"),
+ "--global-config",
+ config_path,
+ "--in-place",
+ "--recursive",
+ ]
+
+ if config.get("exclude"):
+ fix_cmd.extend(["--exclude", ",".join(config["exclude"])])
+
+ subprocess.call(fix_cmd + paths)
+
+ results = run(paths, config, **lintargs)
+
+ fixed = fixed - len(results)
+
+ return {"results": results, "fixed": fixed}
+
+
+def run(paths, config, **lintargs):
+ from flake8 import __version__ as flake8_version
+ from flake8.main.application import Application
+
+ log = lintargs["log"]
+ root = lintargs["root"]
+ config_path = os.path.join(root, ".flake8")
+
+ # Run flake8.
+ app = Application()
+ log.debug("flake8 version={}".format(flake8_version))
+
+ output_file = mozfile.NamedTemporaryFile(mode="r")
+ flake8_cmd = [
+ "--config",
+ config_path,
+ "--output-file",
+ output_file.name,
+ "--format",
+ '{"path":"%(path)s","lineno":%(row)s,'
+ '"column":%(col)s,"rule":"%(code)s","message":"%(text)s"}',
+ "--filename",
+ ",".join(["*.{}".format(e) for e in config["extensions"]]),
+ ]
+ log.debug("Command: {}".format(" ".join(flake8_cmd)))
+
+ orig_make_file_checker_manager = app.make_file_checker_manager
+
+ def wrap_make_file_checker_manager(self):
+ """Flake8 is very inefficient when it comes to applying exclusion
+ rules, using `expand_exclusions` to turn directories into a list of
+ relevant python files is an order of magnitude faster.
+
+ Hooking into flake8 here also gives us a convenient place to merge the
+ `exclude` rules specified in the root .flake8 with the ones added by
+ tools/lint/mach_commands.py.
+ """
+ # Ignore exclude rules if `--no-filter` was passed in.
+ config.setdefault("exclude", [])
+ if lintargs.get("use_filters", True):
+ config["exclude"].extend(map(mozpath.normpath, self.options.exclude))
+
+ # Since we use the root .flake8 file to store exclusions, we haven't
+ # properly filtered the paths through mozlint's `filterpaths` function
+ # yet. This mimics that though there could be other edge cases that are
+ # different. Maybe we should call `filterpaths` directly, though for
+ # now that doesn't appear to be necessary.
+ filtered = [
+ p for p in paths if not any(p.startswith(e) for e in config["exclude"])
+ ]
+
+ self.options.filenames = self.options.filenames + list(
+ expand_exclusions(filtered, config, root)
+ )
+
+ if not self.options.filenames:
+ raise NothingToLint
+ return orig_make_file_checker_manager()
+
+ app.make_file_checker_manager = wrap_make_file_checker_manager.__get__(
+ app, Application
+ )
+
+ # Make sure to run from repository root so exclusions are joined to the
+ # repository root and not the current working directory.
+ oldcwd = os.getcwd()
+ os.chdir(root)
+ try:
+ app.run(flake8_cmd)
+ except NothingToLint:
+ pass
+ finally:
+ os.chdir(oldcwd)
+
+ results = []
+
+ WARNING_RULES = set(config.get("warning-rules", []))
+
+ def process_line(line):
+ # Escape slashes otherwise JSON conversion will not work
+ line = line.replace("\\", "\\\\")
+ try:
+ res = json.loads(line)
+ except ValueError:
+ print("Non JSON output from linter, will not be processed: {}".format(line))
+ return
+
+ if res.get("code") in LINE_OFFSETS:
+ res["lineoffset"] = LINE_OFFSETS[res["code"]]
+
+ if res["rule"] in WARNING_RULES:
+ res["level"] = "warning"
+
+ results.append(result.from_config(config, **res))
+
+ list(map(process_line, output_file.readlines()))
+ return results
diff --git a/tools/lint/python/flake8_requirements.in b/tools/lint/python/flake8_requirements.in
new file mode 100644
index 0000000000..0a9262b9c6
--- /dev/null
+++ b/tools/lint/python/flake8_requirements.in
@@ -0,0 +1,4 @@
+flake8==5.0.4
+zipp==0.5
+autopep8==1.7.0
+typing-extensions==3.10.0.2
diff --git a/tools/lint/python/flake8_requirements.txt b/tools/lint/python/flake8_requirements.txt
new file mode 100644
index 0000000000..36879d20c8
--- /dev/null
+++ b/tools/lint/python/flake8_requirements.txt
@@ -0,0 +1,41 @@
+#
+# This file is autogenerated by pip-compile with python 3.10
+# To update, run:
+#
+# pip-compile --generate-hashes tools/lint/python/flake8_requirements.in
+#
+autopep8==1.7.0 \
+ --hash=sha256:6f09e90a2be784317e84dc1add17ebfc7abe3924239957a37e5040e27d812087 \
+ --hash=sha256:ca9b1a83e53a7fad65d731dc7a2a2d50aa48f43850407c59f6a1a306c4201142
+ # via -r tools/lint/python/flake8_requirements.in
+flake8==5.0.4 \
+ --hash=sha256:6fbe320aad8d6b95cec8b8e47bc933004678dc63095be98528b7bdd2a9f510db \
+ --hash=sha256:7a1cf6b73744f5806ab95e526f6f0d8c01c66d7bbe349562d22dfca20610b248
+ # via -r tools/lint/python/flake8_requirements.in
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via flake8
+pycodestyle==2.9.1 \
+ --hash=sha256:2c9607871d58c76354b697b42f5d57e1ada7d261c261efac224b664affdc5785 \
+ --hash=sha256:d1735fc58b418fd7c5f658d28d943854f8a849b01a5d0a1e6f3f3fdd0166804b
+ # via
+ # autopep8
+ # flake8
+pyflakes==2.5.0 \
+ --hash=sha256:4579f67d887f804e67edb544428f264b7b24f435b263c4614f384135cea553d2 \
+ --hash=sha256:491feb020dca48ccc562a8c0cbe8df07ee13078df59813b83959cbdada312ea3
+ # via flake8
+toml==0.10.2 \
+ --hash=sha256:806143ae5bfb6a3c6e736a764057db0e6a0e05e338b5630894a5f779cabb4f9b \
+ --hash=sha256:b3bda1d108d5dd99f4a20d24d9c348e91c4db7ab1b749200bded2f839ccbe68f
+ # via autopep8
+typing-extensions==3.10.0.2 \
+ --hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
+ --hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \
+ --hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34
+ # via -r tools/lint/python/flake8_requirements.in
+zipp==0.5 \
+ --hash=sha256:46dfd547d9ccbf8bdc26ecea52818046bb28509f12bb6a0de1cd66ab06e9a9be \
+ --hash=sha256:d7ac25f895fb65bff937b381353c14eb1fa23d35f40abd72a5342cd57eb57fd1
+ # via -r tools/lint/python/flake8_requirements.in
diff --git a/tools/lint/python/isort.py b/tools/lint/python/isort.py
new file mode 100644
index 0000000000..9af0e42d10
--- /dev/null
+++ b/tools/lint/python/isort.py
@@ -0,0 +1,138 @@
+# 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 configparser
+import os
+import platform
+import re
+import signal
+import subprocess
+import sys
+
+import mozpack.path as mozpath
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+from mozprocess import ProcessHandler
+
+here = os.path.abspath(os.path.dirname(__file__))
+ISORT_REQUIREMENTS_PATH = os.path.join(here, "isort_requirements.txt")
+
+ISORT_INSTALL_ERROR = """
+Unable to install correct version of isort
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ ISORT_REQUIREMENTS_PATH
+)
+
+
+def default_bindir():
+ # We use sys.prefix to find executables as that gets modified with
+ # virtualenv's activate_this.py, whereas sys.executable doesn't.
+ if platform.system() == "Windows":
+ return os.path.join(sys.prefix, "Scripts")
+ return os.path.join(sys.prefix, "bin")
+
+
+def parse_issues(config, output, *, log):
+ would_sort = re.compile(
+ "^ERROR: (.*?) Imports are incorrectly sorted and/or formatted.$", re.I
+ )
+ sorted = re.compile("^Fixing (.*)$", re.I)
+ results = []
+ for line in output:
+ line = line.decode("utf-8")
+
+ match = would_sort.match(line)
+ if match:
+ res = {"path": match.group(1)}
+ results.append(result.from_config(config, **res))
+ continue
+
+ match = sorted.match(line)
+ if match:
+ res = {"path": match.group(1), "message": "sorted"}
+ results.append(result.from_config(config, **res))
+ continue
+
+ log.debug("Unhandled line", line)
+ return results
+
+
+class IsortProcess(ProcessHandler):
+ def __init__(self, config, *args, **kwargs):
+ self.config = config
+ kwargs["stream"] = False
+ ProcessHandler.__init__(self, *args, **kwargs)
+
+ def run(self, *args, **kwargs):
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ ProcessHandler.run(self, *args, **kwargs)
+ signal.signal(signal.SIGINT, orig)
+
+
+def run_process(config, cmd):
+ proc = IsortProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+ return proc.output
+
+
+def setup(root, **lintargs):
+ virtualenv_manager = lintargs["virtualenv_manager"]
+ try:
+ virtualenv_manager.install_pip_requirements(ISORT_REQUIREMENTS_PATH, quiet=True)
+ except subprocess.CalledProcessError:
+ print(ISORT_INSTALL_ERROR)
+ return 1
+
+
+def lint(paths, config, **lintargs):
+ from isort import __version__ as isort_version
+
+ binary = os.path.join(
+ lintargs.get("virtualenv_bin_path") or default_bindir(), "isort"
+ )
+
+ log = lintargs["log"]
+ root = lintargs["root"]
+
+ log.debug("isort version {}".format(isort_version))
+
+ cmd_args = [
+ binary,
+ "--resolve-all-configs",
+ "--config-root",
+ root,
+ ]
+ if not lintargs.get("fix"):
+ cmd_args.append("--check-only")
+
+ # We merge exclusion rules from .flake8 to avoid having to repeat the same exclusions twice.
+ flake8_config_path = os.path.join(root, ".flake8")
+ flake8_config = configparser.ConfigParser()
+ flake8_config.read(flake8_config_path)
+ config["exclude"].extend(
+ mozpath.normpath(p.strip())
+ for p in flake8_config.get("flake8", "exclude").split(",")
+ )
+
+ paths = list(expand_exclusions(paths, config, lintargs["root"]))
+ if len(paths) == 0:
+ return {"results": [], "fixed": 0}
+
+ base_command = cmd_args + paths
+ log.debug("Command: {}".format(" ".join(base_command)))
+
+ output = run_process(config, base_command)
+
+ results = parse_issues(config, output, log=log)
+
+ fixed = sum(1 for issue in results if issue.message == "sorted")
+
+ return {"results": results, "fixed": fixed}
diff --git a/tools/lint/python/isort_requirements.in b/tools/lint/python/isort_requirements.in
new file mode 100644
index 0000000000..8eeb146b1a
--- /dev/null
+++ b/tools/lint/python/isort_requirements.in
@@ -0,0 +1 @@
+isort==5.10.1
diff --git a/tools/lint/python/isort_requirements.txt b/tools/lint/python/isort_requirements.txt
new file mode 100644
index 0000000000..84df6d2093
--- /dev/null
+++ b/tools/lint/python/isort_requirements.txt
@@ -0,0 +1,10 @@
+#
+# This file is autogenerated by pip-compile with python 3.10
+# To update, run:
+#
+# pip-compile --generate-hashes --output-file=tools/lint/python/isort_requirements.txt tools/lint/python/isort_requirements.in
+#
+isort==5.10.1 \
+ --hash=sha256:6f62d78e2f89b4500b080fe3a81690850cd254227f27f75c3a0c491a1f351ba7 \
+ --hash=sha256:e8443a5e7a020e9d7f97f1d7d9cd17c88bcb3bc7e218bf9cf5095fe550be2951
+ # via -r tools/lint/python/isort_requirements.in
diff --git a/tools/lint/python/l10n_lint.py b/tools/lint/python/l10n_lint.py
new file mode 100644
index 0000000000..ef3269ef2a
--- /dev/null
+++ b/tools/lint/python/l10n_lint.py
@@ -0,0 +1,171 @@
+# 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
+from datetime import datetime, timedelta
+
+import mozversioncontrol.repoupdate
+from compare_locales import parser
+from compare_locales.lint.linter import L10nLinter
+from compare_locales.lint.util import l10n_base_reference_and_tests
+from compare_locales.paths import ProjectFiles, TOMLParser
+from mach import util as mach_util
+from mozlint import pathutils, result
+from mozpack import path as mozpath
+
+LOCALE = "gecko-strings"
+STRINGS_REPO = "https://hg.mozilla.org/l10n/gecko-strings"
+
+PULL_AFTER = timedelta(days=2)
+
+# Wrapper to call lint_strings with mozilla-central configuration
+# comm-central defines its own wrapper since comm-central strings are
+# in separate repositories
+def lint(paths, lintconfig, **lintargs):
+ return lint_strings(LOCALE, paths, lintconfig, **lintargs)
+
+
+def lint_strings(locale, paths, lintconfig, **lintargs):
+ l10n_base = mach_util.get_state_dir()
+ root = lintargs["root"]
+ exclude = lintconfig.get("exclude")
+ extensions = lintconfig.get("extensions")
+
+ # Load l10n.toml configs
+ l10nconfigs = load_configs(lintconfig, root, l10n_base, locale)
+
+ # Check include paths in l10n.yml if it's in our given paths
+ # Only the l10n.yml will show up here, but if the l10n.toml files
+ # change, we also get the l10n.yml as the toml files are listed as
+ # support files.
+ if lintconfig["path"] in paths:
+ results = validate_linter_includes(lintconfig, l10nconfigs, lintargs)
+ paths.remove(lintconfig["path"])
+ else:
+ results = []
+
+ all_files = []
+ for p in paths:
+ fp = pathutils.FilterPath(p)
+ if fp.isdir:
+ for _, fileobj in fp.finder:
+ all_files.append(fileobj.path)
+ if fp.isfile:
+ all_files.append(p)
+ # Filter again, our directories might have picked up files the
+ # explicitly excluded in the l10n.yml configuration.
+ # `browser/locales/en-US/firefox-l10n.js` is a good example.
+ all_files, _ = pathutils.filterpaths(
+ lintargs["root"],
+ all_files,
+ lintconfig["include"],
+ exclude=exclude,
+ extensions=extensions,
+ )
+ # These should be excluded in l10n.yml
+ skips = {p for p in all_files if not parser.hasParser(p)}
+ results.extend(
+ result.from_config(
+ lintconfig,
+ level="warning",
+ path=path,
+ message="file format not supported in compare-locales",
+ )
+ for path in skips
+ )
+ all_files = [p for p in all_files if p not in skips]
+ files = ProjectFiles(locale, l10nconfigs)
+
+ get_reference_and_tests = l10n_base_reference_and_tests(files)
+
+ linter = MozL10nLinter(lintconfig)
+ results += linter.lint(all_files, get_reference_and_tests)
+ return results
+
+
+# Similar to the lint/lint_strings wrapper setup, for comm-central support.
+def gecko_strings_setup(**lint_args):
+ return strings_repo_setup(STRINGS_REPO, LOCALE)
+
+
+def strings_repo_setup(repo, locale):
+ gs = mozpath.join(mach_util.get_state_dir(), locale)
+ marker = mozpath.join(gs, ".hg", "l10n_pull_marker")
+ try:
+ last_pull = datetime.fromtimestamp(os.stat(marker).st_mtime)
+ skip_clone = datetime.now() < last_pull + PULL_AFTER
+ except OSError:
+ skip_clone = False
+ if skip_clone:
+ return
+ try:
+ hg = mozversioncontrol.get_tool_path("hg")
+ except mozversioncontrol.MissingVCSTool:
+ if os.environ.get("MOZ_AUTOMATION"):
+ raise
+ print("warning: l10n linter requires Mercurial but was unable to find 'hg'")
+ return 1
+ mozversioncontrol.repoupdate.update_mercurial_repo(hg, repo, gs)
+ with open(marker, "w") as fh:
+ fh.flush()
+
+
+def load_configs(lintconfig, root, l10n_base, locale):
+ """Load l10n configuration files specified in the linter configuration."""
+ configs = []
+ env = {"l10n_base": l10n_base}
+ for toml in lintconfig["l10n_configs"]:
+ cfg = TOMLParser().parse(
+ mozpath.join(root, toml), env=env, ignore_missing_includes=True
+ )
+ cfg.set_locales([locale], deep=True)
+ configs.append(cfg)
+ return configs
+
+
+def validate_linter_includes(lintconfig, l10nconfigs, lintargs):
+ """Check l10n.yml config against l10n.toml configs."""
+ reference_paths = set(
+ mozpath.relpath(p["reference"].prefix, lintargs["root"])
+ for project in l10nconfigs
+ for config in project.configs
+ for p in config.paths
+ )
+ # Just check for directories
+ reference_dirs = sorted(p for p in reference_paths if os.path.isdir(p))
+ missing_in_yml = [
+ refd for refd in reference_dirs if refd not in lintconfig["include"]
+ ]
+ # These might be subdirectories in the config, though
+ missing_in_yml = [
+ d
+ for d in missing_in_yml
+ if not any(d.startswith(parent + "/") for parent in lintconfig["include"])
+ ]
+ if missing_in_yml:
+ dirs = ", ".join(missing_in_yml)
+ return [
+ result.from_config(
+ lintconfig,
+ path=lintconfig["path"],
+ message="l10n.yml out of sync with l10n.toml, add: " + dirs,
+ )
+ ]
+ return []
+
+
+class MozL10nLinter(L10nLinter):
+ """Subclass linter to generate the right result type."""
+
+ def __init__(self, lintconfig):
+ super(MozL10nLinter, self).__init__()
+ self.lintconfig = lintconfig
+
+ def lint(self, files, get_reference_and_tests):
+ return [
+ result.from_config(self.lintconfig, **result_data)
+ for result_data in super(MozL10nLinter, self).lint(
+ files, get_reference_and_tests
+ )
+ ]
diff --git a/tools/lint/python/pylint.py b/tools/lint/python/pylint.py
new file mode 100644
index 0000000000..8bb0c68b87
--- /dev/null
+++ b/tools/lint/python/pylint.py
@@ -0,0 +1,133 @@
+# 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 json
+import os
+import signal
+import subprocess
+
+from mach.site import InstallPipRequirementsException
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+from mozprocess import ProcessHandler
+
+here = os.path.abspath(os.path.dirname(__file__))
+PYLINT_REQUIREMENTS_PATH = os.path.join(here, "pylint_requirements.txt")
+
+PYLINT_NOT_FOUND = """
+Could not find pylint! Install pylint and try again.
+
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ PYLINT_REQUIREMENTS_PATH
+)
+
+
+PYLINT_INSTALL_ERROR = """
+Unable to install correct version of pylint
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ PYLINT_REQUIREMENTS_PATH
+)
+
+
+class PylintProcess(ProcessHandler):
+ def __init__(self, config, *args, **kwargs):
+ self.config = config
+ kwargs["stream"] = False
+ kwargs["universal_newlines"] = True
+ ProcessHandler.__init__(self, *args, **kwargs)
+
+ def run(self, *args, **kwargs):
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ ProcessHandler.run(self, *args, **kwargs)
+ signal.signal(signal.SIGINT, orig)
+
+
+def setup(root, **lintargs):
+ virtualenv_manager = lintargs["virtualenv_manager"]
+ try:
+ virtualenv_manager.install_pip_requirements(
+ PYLINT_REQUIREMENTS_PATH,
+ quiet=True,
+ )
+ except (subprocess.CalledProcessError, InstallPipRequirementsException):
+ print(PYLINT_INSTALL_ERROR)
+ return 1
+
+
+def get_pylint_binary():
+ return "pylint"
+
+
+def run_process(config, cmd):
+ proc = PylintProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+ return proc.output
+
+
+def parse_issues(log, config, issues_json, path):
+ results = []
+
+ try:
+ issues = json.loads(issues_json)
+ except json.decoder.JSONDecodeError:
+ log.debug("Could not parse the output:")
+ log.debug("pylint output: {}".format(issues_json))
+ return []
+
+ for issue in issues:
+ res = {
+ "path": issue["path"],
+ "level": issue["type"],
+ "lineno": issue["line"],
+ "column": issue["column"],
+ "message": issue["message"],
+ "rule": issue["message-id"],
+ }
+ results.append(result.from_config(config, **res))
+ return results
+
+
+def get_pylint_version(binary):
+ return subprocess.check_output(
+ [binary, "--version"],
+ universal_newlines=True,
+ stderr=subprocess.STDOUT,
+ )
+
+
+def lint(paths, config, **lintargs):
+ log = lintargs["log"]
+
+ binary = get_pylint_binary()
+
+ log = lintargs["log"]
+ paths = list(expand_exclusions(paths, config, lintargs["root"]))
+
+ cmd_args = [binary]
+ results = []
+
+ # list from https://code.visualstudio.com/docs/python/linting#_pylint
+ # And ignore a bit more elements
+ cmd_args += [
+ "-fjson",
+ "--disable=all",
+ "--enable=F,E,unreachable,duplicate-key,unnecessary-semicolon,global-variable-not-assigned,unused-variable,binary-op-exception,bad-format-string,anomalous-backslash-in-string,bad-open-mode,no-else-return", # NOQA: E501
+ "--disable=import-error,no-member",
+ ]
+
+ base_command = cmd_args + paths
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ log.debug("pylint version: {}".format(get_pylint_version(binary)))
+ output = " ".join(run_process(config, base_command))
+ results = parse_issues(log, config, str(output), [])
+
+ return results
diff --git a/tools/lint/python/pylint_requirements.in b/tools/lint/python/pylint_requirements.in
new file mode 100644
index 0000000000..c584cdf2a6
--- /dev/null
+++ b/tools/lint/python/pylint_requirements.in
@@ -0,0 +1,5 @@
+pylint==2.15.8
+dill==0.3.4
+tomli==1.2.2
+typing-extensions==3.10.0.2
+tomlkit==0.10.1
diff --git a/tools/lint/python/pylint_requirements.txt b/tools/lint/python/pylint_requirements.txt
new file mode 100644
index 0000000000..093b1ce402
--- /dev/null
+++ b/tools/lint/python/pylint_requirements.txt
@@ -0,0 +1,136 @@
+#
+# This file is autogenerated by pip-compile with python 3.10
+# To update, run:
+#
+# pip-compile --generate-hashes tools/lint/python/pylint_requirements.in
+#
+astroid==2.12.13 \
+ --hash=sha256:10e0ad5f7b79c435179d0d0f0df69998c4eef4597534aae44910db060baeb907 \
+ --hash=sha256:1493fe8bd3dfd73dc35bd53c9d5b6e49ead98497c47b2307662556a5692d29d7
+ # via pylint
+dill==0.3.4 \
+ --hash=sha256:7e40e4a70304fd9ceab3535d36e58791d9c4a776b38ec7f7ec9afc8d3dca4d4f \
+ --hash=sha256:9f9734205146b2b353ab3fec9af0070237b6ddae78452af83d2fca84d739e675
+ # via
+ # -r tools/lint/python/pylint_requirements.in
+ # pylint
+isort==5.11.2 \
+ --hash=sha256:dd8bbc5c0990f2a095d754e50360915f73b4c26fc82733eb5bfc6b48396af4d2 \
+ --hash=sha256:e486966fba83f25b8045f8dd7455b0a0d1e4de481e1d7ce4669902d9fb85e622
+ # via pylint
+lazy-object-proxy==1.8.0 \
+ --hash=sha256:0c1c7c0433154bb7c54185714c6929acc0ba04ee1b167314a779b9025517eada \
+ --hash=sha256:14010b49a2f56ec4943b6cf925f597b534ee2fe1f0738c84b3bce0c1a11ff10d \
+ --hash=sha256:4e2d9f764f1befd8bdc97673261b8bb888764dfdbd7a4d8f55e4fbcabb8c3fb7 \
+ --hash=sha256:4fd031589121ad46e293629b39604031d354043bb5cdf83da4e93c2d7f3389fe \
+ --hash=sha256:5b51d6f3bfeb289dfd4e95de2ecd464cd51982fe6f00e2be1d0bf94864d58acd \
+ --hash=sha256:6850e4aeca6d0df35bb06e05c8b934ff7c533734eb51d0ceb2d63696f1e6030c \
+ --hash=sha256:6f593f26c470a379cf7f5bc6db6b5f1722353e7bf937b8d0d0b3fba911998858 \
+ --hash=sha256:71d9ae8a82203511a6f60ca5a1b9f8ad201cac0fc75038b2dc5fa519589c9288 \
+ --hash=sha256:7e1561626c49cb394268edd00501b289053a652ed762c58e1081224c8d881cec \
+ --hash=sha256:8f6ce2118a90efa7f62dd38c7dbfffd42f468b180287b748626293bf12ed468f \
+ --hash=sha256:ae032743794fba4d171b5b67310d69176287b5bf82a21f588282406a79498891 \
+ --hash=sha256:afcaa24e48bb23b3be31e329deb3f1858f1f1df86aea3d70cb5c8578bfe5261c \
+ --hash=sha256:b70d6e7a332eb0217e7872a73926ad4fdc14f846e85ad6749ad111084e76df25 \
+ --hash=sha256:c219a00245af0f6fa4e95901ed28044544f50152840c5b6a3e7b2568db34d156 \
+ --hash=sha256:ce58b2b3734c73e68f0e30e4e725264d4d6be95818ec0a0be4bb6bf9a7e79aa8 \
+ --hash=sha256:d176f392dbbdaacccf15919c77f526edf11a34aece58b55ab58539807b85436f \
+ --hash=sha256:e20bfa6db17a39c706d24f82df8352488d2943a3b7ce7d4c22579cb89ca8896e \
+ --hash=sha256:eac3a9a5ef13b332c059772fd40b4b1c3d45a3a2b05e33a361dee48e54a4dad0 \
+ --hash=sha256:eb329f8d8145379bf5dbe722182410fe8863d186e51bf034d2075eb8d85ee25b
+ # via astroid
+mccabe==0.7.0 \
+ --hash=sha256:348e0240c33b60bbdf4e523192ef919f28cb2c3d7d5c7794f74009290f236325 \
+ --hash=sha256:6c2d30ab6be0e4a46919781807b4f0d834ebdd6c6e3dca0bda5a15f863427b6e
+ # via pylint
+platformdirs==2.6.0 \
+ --hash=sha256:1a89a12377800c81983db6be069ec068eee989748799b946cce2a6e80dcc54ca \
+ --hash=sha256:b46ffafa316e6b83b47489d240ce17173f123a9b9c83282141c3daf26ad9ac2e
+ # via pylint
+pylint==2.15.8 \
+ --hash=sha256:ea82cd6a1e11062dc86d555d07c021b0fb65afe39becbe6fe692efd6c4a67443 \
+ --hash=sha256:ec4a87c33da054ab86a6c79afa6771dc8765cb5631620053e727fcf3ef8cbed7
+ # via -r tools/lint/python/pylint_requirements.in
+tomli==1.2.2 \
+ --hash=sha256:c6ce0015eb38820eaf32b5db832dbc26deb3dd427bd5f6556cf0acac2c214fee \
+ --hash=sha256:f04066f68f5554911363063a30b108d2b5a5b1a010aa8b6132af78489fe3aade
+ # via
+ # -r tools/lint/python/pylint_requirements.in
+ # pylint
+tomlkit==0.10.1 \
+ --hash=sha256:3c517894eadef53e9072d343d37e4427b8f0b6200a70b7c9a19b2ebd1f53b951 \
+ --hash=sha256:3eba517439dcb2f84cf39f4f85fd2c3398309823a3c75ac3e73003638daf7915
+ # via
+ # -r tools/lint/python/pylint_requirements.in
+ # pylint
+typing-extensions==3.10.0.2 \
+ --hash=sha256:49f75d16ff11f1cd258e1b988ccff82a3ca5570217d7ad8c5f48205dd99a677e \
+ --hash=sha256:d8226d10bc02a29bcc81df19a26e56a9647f8b0a6d4a83924139f4a8b01f17b7 \
+ --hash=sha256:f1d25edafde516b146ecd0613dabcc61409817af4766fbbcfb8d1ad4ec441a34
+ # via -r tools/lint/python/pylint_requirements.in
+wrapt==1.14.1 \
+ --hash=sha256:00b6d4ea20a906c0ca56d84f93065b398ab74b927a7a3dbd470f6fc503f95dc3 \
+ --hash=sha256:01c205616a89d09827986bc4e859bcabd64f5a0662a7fe95e0d359424e0e071b \
+ --hash=sha256:02b41b633c6261feff8ddd8d11c711df6842aba629fdd3da10249a53211a72c4 \
+ --hash=sha256:07f7a7d0f388028b2df1d916e94bbb40624c59b48ecc6cbc232546706fac74c2 \
+ --hash=sha256:11871514607b15cfeb87c547a49bca19fde402f32e2b1c24a632506c0a756656 \
+ --hash=sha256:1b376b3f4896e7930f1f772ac4b064ac12598d1c38d04907e696cc4d794b43d3 \
+ --hash=sha256:21ac0156c4b089b330b7666db40feee30a5d52634cc4560e1905d6529a3897ff \
+ --hash=sha256:257fd78c513e0fb5cdbe058c27a0624c9884e735bbd131935fd49e9fe719d310 \
+ --hash=sha256:2b39d38039a1fdad98c87279b48bc5dce2c0ca0d73483b12cb72aa9609278e8a \
+ --hash=sha256:2cf71233a0ed05ccdabe209c606fe0bac7379fdcf687f39b944420d2a09fdb57 \
+ --hash=sha256:2fe803deacd09a233e4762a1adcea5db5d31e6be577a43352936179d14d90069 \
+ --hash=sha256:3232822c7d98d23895ccc443bbdf57c7412c5a65996c30442ebe6ed3df335383 \
+ --hash=sha256:34aa51c45f28ba7f12accd624225e2b1e5a3a45206aa191f6f9aac931d9d56fe \
+ --hash=sha256:36f582d0c6bc99d5f39cd3ac2a9062e57f3cf606ade29a0a0d6b323462f4dd87 \
+ --hash=sha256:380a85cf89e0e69b7cfbe2ea9f765f004ff419f34194018a6827ac0e3edfed4d \
+ --hash=sha256:40e7bc81c9e2b2734ea4bc1aceb8a8f0ceaac7c5299bc5d69e37c44d9081d43b \
+ --hash=sha256:43ca3bbbe97af00f49efb06e352eae40434ca9d915906f77def219b88e85d907 \
+ --hash=sha256:4fcc4649dc762cddacd193e6b55bc02edca674067f5f98166d7713b193932b7f \
+ --hash=sha256:5a0f54ce2c092aaf439813735584b9537cad479575a09892b8352fea5e988dc0 \
+ --hash=sha256:5a9a0d155deafd9448baff28c08e150d9b24ff010e899311ddd63c45c2445e28 \
+ --hash=sha256:5b02d65b9ccf0ef6c34cba6cf5bf2aab1bb2f49c6090bafeecc9cd81ad4ea1c1 \
+ --hash=sha256:60db23fa423575eeb65ea430cee741acb7c26a1365d103f7b0f6ec412b893853 \
+ --hash=sha256:642c2e7a804fcf18c222e1060df25fc210b9c58db7c91416fb055897fc27e8cc \
+ --hash=sha256:6a9a25751acb379b466ff6be78a315e2b439d4c94c1e99cb7266d40a537995d3 \
+ --hash=sha256:6b1a564e6cb69922c7fe3a678b9f9a3c54e72b469875aa8018f18b4d1dd1adf3 \
+ --hash=sha256:6d323e1554b3d22cfc03cd3243b5bb815a51f5249fdcbb86fda4bf62bab9e164 \
+ --hash=sha256:6e743de5e9c3d1b7185870f480587b75b1cb604832e380d64f9504a0535912d1 \
+ --hash=sha256:709fe01086a55cf79d20f741f39325018f4df051ef39fe921b1ebe780a66184c \
+ --hash=sha256:7b7c050ae976e286906dd3f26009e117eb000fb2cf3533398c5ad9ccc86867b1 \
+ --hash=sha256:7d2872609603cb35ca513d7404a94d6d608fc13211563571117046c9d2bcc3d7 \
+ --hash=sha256:7ef58fb89674095bfc57c4069e95d7a31cfdc0939e2a579882ac7d55aadfd2a1 \
+ --hash=sha256:80bb5c256f1415f747011dc3604b59bc1f91c6e7150bd7db03b19170ee06b320 \
+ --hash=sha256:81b19725065dcb43df02b37e03278c011a09e49757287dca60c5aecdd5a0b8ed \
+ --hash=sha256:833b58d5d0b7e5b9832869f039203389ac7cbf01765639c7309fd50ef619e0b1 \
+ --hash=sha256:88bd7b6bd70a5b6803c1abf6bca012f7ed963e58c68d76ee20b9d751c74a3248 \
+ --hash=sha256:8ad85f7f4e20964db4daadcab70b47ab05c7c1cf2a7c1e51087bfaa83831854c \
+ --hash=sha256:8c0ce1e99116d5ab21355d8ebe53d9460366704ea38ae4d9f6933188f327b456 \
+ --hash=sha256:8d649d616e5c6a678b26d15ece345354f7c2286acd6db868e65fcc5ff7c24a77 \
+ --hash=sha256:903500616422a40a98a5a3c4ff4ed9d0066f3b4c951fa286018ecdf0750194ef \
+ --hash=sha256:9736af4641846491aedb3c3f56b9bc5568d92b0692303b5a305301a95dfd38b1 \
+ --hash=sha256:988635d122aaf2bdcef9e795435662bcd65b02f4f4c1ae37fbee7401c440b3a7 \
+ --hash=sha256:9cca3c2cdadb362116235fdbd411735de4328c61425b0aa9f872fd76d02c4e86 \
+ --hash=sha256:9e0fd32e0148dd5dea6af5fee42beb949098564cc23211a88d799e434255a1f4 \
+ --hash=sha256:9f3e6f9e05148ff90002b884fbc2a86bd303ae847e472f44ecc06c2cd2fcdb2d \
+ --hash=sha256:a85d2b46be66a71bedde836d9e41859879cc54a2a04fad1191eb50c2066f6e9d \
+ --hash=sha256:a9a52172be0b5aae932bef82a79ec0a0ce87288c7d132946d645eba03f0ad8a8 \
+ --hash=sha256:aa31fdcc33fef9eb2552cbcbfee7773d5a6792c137b359e82879c101e98584c5 \
+ --hash=sha256:b014c23646a467558be7da3d6b9fa409b2c567d2110599b7cf9a0c5992b3b471 \
+ --hash=sha256:b21bb4c09ffabfa0e85e3a6b623e19b80e7acd709b9f91452b8297ace2a8ab00 \
+ --hash=sha256:b5901a312f4d14c59918c221323068fad0540e34324925c8475263841dbdfe68 \
+ --hash=sha256:b9b7a708dd92306328117d8c4b62e2194d00c365f18eff11a9b53c6f923b01e3 \
+ --hash=sha256:d1967f46ea8f2db647c786e78d8cc7e4313dbd1b0aca360592d8027b8508e24d \
+ --hash=sha256:d52a25136894c63de15a35bc0bdc5adb4b0e173b9c0d07a2be9d3ca64a332735 \
+ --hash=sha256:d77c85fedff92cf788face9bfa3ebaa364448ebb1d765302e9af11bf449ca36d \
+ --hash=sha256:d79d7d5dc8a32b7093e81e97dad755127ff77bcc899e845f41bf71747af0c569 \
+ --hash=sha256:dbcda74c67263139358f4d188ae5faae95c30929281bc6866d00573783c422b7 \
+ --hash=sha256:ddaea91abf8b0d13443f6dac52e89051a5063c7d014710dcb4d4abb2ff811a59 \
+ --hash=sha256:dee0ce50c6a2dd9056c20db781e9c1cfd33e77d2d569f5d1d9321c641bb903d5 \
+ --hash=sha256:dee60e1de1898bde3b238f18340eec6148986da0455d8ba7848d50470a7a32fb \
+ --hash=sha256:e2f83e18fe2f4c9e7db597e988f72712c0c3676d337d8b101f6758107c42425b \
+ --hash=sha256:e3fb1677c720409d5f671e39bac6c9e0e422584e5f518bfd50aa4cbbea02433f \
+ --hash=sha256:ee2b1b1769f6707a8a445162ea16dddf74285c3964f605877a20e38545c3c462 \
+ --hash=sha256:ee6acae74a2b91865910eef5e7de37dc6895ad96fa23603d1d27ea69df545015 \
+ --hash=sha256:ef3f72c9666bba2bab70d2a8b79f2c6d2c1a42a7f7e2b0ec83bb2f9e383950af
+ # via astroid
diff --git a/tools/lint/rejected-words.yml b/tools/lint/rejected-words.yml
new file mode 100644
index 0000000000..b6b6419a79
--- /dev/null
+++ b/tools/lint/rejected-words.yml
@@ -0,0 +1,469 @@
+---
+avoid-blacklist-and-whitelist:
+ description: "Use words like 'skip', 'select', 'allow' or 'deny' instead"
+ level: error
+ include: ['.']
+ type: regex
+ payload: (black|white)[-_]?list
+ ignore-case: true
+ # Based on codespell with idl and webidl added.
+ extensions:
+ - js
+ - jsm
+ - mjs
+ - jxs
+ - idl
+ - webidl
+ - xml
+ - html
+ - xhtml
+ - cpp
+ - c
+ - h
+ - configure
+ - py
+ - properties
+ - rst
+ - md
+ - ftl
+ - yml
+ - java
+ - kt
+ exclude:
+ - '**/.eslintrc.js'
+ - browser/app/profile/firefox.js
+ - browser/app/winlauncher/LauncherProcessWin.cpp
+ - browser/base/content/browser.js
+ - browser/base/content/contentTheme.js
+ - browser/base/content/test/general/browser_remoteTroubleshoot.js
+ - browser/base/content/test/general/browser_tab_drag_drop_perwindow.js
+ - browser/base/content/test/performance/browser_preferences_usage.js
+ - browser/base/content/test/protectionsUI/browser_protectionsUI_cryptominers.js
+ - browser/base/content/test/protectionsUI/browser_protectionsUI_fingerprinters.js
+ - browser/base/content/test/protectionsUI/browser_protectionsUI_pbmode_exceptions.js
+ - browser/base/content/test/protectionsUI/browser_protectionsUI_report_breakage.js
+ - browser/base/content/test/protectionsUI/browser_protectionsUI_socialtracking.js
+ - browser/base/content/test/protectionsUI/browser_protectionsUI_state.js
+ - browser/base/content/test/protectionsUI/browser_protectionsUI_subview_shim.js
+ - browser/base/content/test/siteIdentity/browser_no_mcb_for_loopback.js
+ - browser/base/content/test/siteIdentity/browser_no_mcb_for_onions.js
+ - browser/base/content/test/static/browser_all_files_referenced.js
+ - browser/base/content/test/static/browser_misused_characters_in_strings.js
+ - browser/base/content/test/static/browser_parsable_css.js
+ - browser/base/content/test/static/browser_parsable_script.js
+ - browser/base/content/test/tabMediaIndicator/browser_mute_webAudio.js
+ - browser/base/content/test/tabs/browser_new_file_whitelisted_http_tab.js
+ - browser/components/downloads/content/contentAreaDownloadsView.xhtml
+ - browser/components/migration/ChromeMigrationUtils.sys.mjs
+ - browser/components/migration/ChromeProfileMigrator.sys.mjs
+ - browser/components/newtab/data/content/activity-stream.bundle.js
+ - browser/components/ion/content/ion.js
+ - browser/components/preferences/privacy.inc.xhtml
+ - browser/components/preferences/privacy.js
+ - browser/components/resistfingerprinting/test/mochitest/test_bug1354633_media_error.html
+ - browser/components/safebrowsing/content/test/browser_whitelisted.js
+ - browser/components/sessionstore/ContentSessionStore.sys.mjs
+ - browser/components/sessionstore/test/browser_crashedTabs.js
+ - browser/components/uitour/UITourChild.jsm
+ - browser/components/urlbar/tests/browser/browser_searchSingleWordNotification.js
+ - browser/components/urlbar/tests/browser/browser_UrlbarInput_trimURLs.js
+ - browser/components/urlbar/tests/unit/test_providerHeuristicFallback.js
+ - browser/components/urlbar/tests/unit/test_search_suggestions.js
+ - browser/components/urlbar/tests/unit/test_tokenizer.js
+ - browser/extensions/screenshots/background/main.js
+ - browser/extensions/webcompat/shims/nielsen.js
+ - browser/modules/SitePermissions.jsm
+ - browser/tools/mozscreenshots/mozscreenshots/extension/configurations/PermissionPrompts.jsm
+ - build/clang-plugin/CustomMatchers.h
+ - build/clang-plugin/FopenUsageChecker.cpp
+ - build/clang-plugin/NaNExprChecker.cpp
+ - build/clang-plugin/NoPrincipalGetURI.cpp
+ - build/clang-plugin/tests/TestNANTestingExpr.cpp
+ - build/compare-mozconfig/compare-mozconfigs.py
+ - build/moz.configure/bindgen.configure
+ - build/moz.configure/toolchain.configure
+ - config/check_vanilla_allocations.py
+ - devtools/client/debugger/dist/parser-worker.js
+ - devtools/client/debugger/test/mochitest/examples/big-sourcemap_files/bundle.js
+ - devtools/client/debugger/test/mochitest/examples/ember/quickstart/dist/assets/vendor.js
+ - devtools/client/debugger/test/mochitest/examples/react/build/main.js
+ - devtools/client/debugger/test/mochitest/examples/react/build/service-worker.js
+ - devtools/client/inspector/markup/test/lib_babel_6.21.0_min.js
+ - devtools/client/inspector/markup/test/lib_react_dom_15.4.1.js
+ - docshell/base/nsDocShell.cpp
+ - docshell/base/URIFixup.sys.mjs
+ - docshell/test/unit/test_URIFixup_info.js
+ - dom/base/Document.cpp
+ - dom/base/MaybeCrossOriginObject.cpp
+ - dom/base/MutationObservers.cpp
+ - dom/base/nsContentUtils.cpp
+ - dom/base/nsContentUtils.h
+ - dom/base/nsDataDocumentContentPolicy.cpp
+ - dom/base/nsGlobalWindowOuter.cpp
+ - dom/base/nsGlobalWindowOuter.h
+ - dom/base/nsTreeSanitizer.cpp
+ - dom/base/nsTreeSanitizer.h
+ - dom/base/test/browser_multiple_popups.js
+ - dom/base/test/browser_timeout_throttling_with_audio_playback.js
+ - dom/base/test/chrome/test_permission_hasValidTransientUserActivation.xhtml
+ - dom/bindings/Codegen.py
+ - dom/bindings/parser/WebIDL.py
+ - dom/bindings/RemoteObjectProxy.cpp
+ - dom/canvas/WebGLContext.cpp
+ - dom/events/EventStateManager.cpp
+ - dom/events/KeyboardEvent.cpp
+ - dom/html/MediaError.cpp
+ - dom/indexedDB/ActorsParent.cpp
+ - dom/ipc/ContentParent.cpp
+ - dom/ipc/fuzztest/content_parent_ipc_libfuzz.cpp
+ - dom/ipc/URLClassifierParent.cpp
+ - dom/media/autoplay/AutoplayPolicy.cpp
+ - dom/media/gmp/GMPChild.cpp
+ - dom/media/ipc/RDDProcessManager.cpp
+ - dom/media/ipc/RemoteDecoderManagerParent.cpp
+ - dom/media/MediaManager.cpp
+ - dom/media/mp4/MP4Decoder.cpp
+ - dom/media/platforms/apple/AppleVTDecoder.cpp
+ - dom/media/platforms/wmf/DXVA2Manager.cpp
+ - dom/media/platforms/wmf/WMFVideoMFTManager.cpp
+ - dom/media/autoplay/test/mochitest/file_autoplay_policy_key_blacklist.html
+ - dom/media/autoplay/test/mochitest/test_autoplay_policy_key_blacklist.html
+ - dom/media/autoplay/test/mochitest/test_autoplay_policy_permission.html
+ - dom/media/webm/WebMDecoder.cpp
+ - dom/media/webrtc/libwebrtcglue/VideoConduit.cpp
+ - dom/media/webrtc/transport/stun_socket_filter.cpp
+ - dom/media/webrtc/transport/test/ice_unittest.cpp
+ - dom/plugins/base/nsPluginHost.h
+ - dom/security/nsCSPContext.cpp
+ - dom/security/nsCSPService.cpp
+ - dom/security/nsCSPUtils.cpp
+ - dom/security/nsCSPUtils.h
+ - dom/security/nsMixedContentBlocker.cpp
+ - dom/security/ReferrerInfo.cpp
+ - dom/security/ReferrerInfo.h
+ - dom/security/test/csp/file_bug802872.js
+ - dom/security/test/csp/file_ignore_unsafe_inline.html
+ - dom/security/test/csp/file_nonce_source.html
+ - dom/security/test/csp/test_blob_data_schemes.html
+ - dom/security/test/csp/test_bug802872.html
+ - dom/security/test/csp/test_nonce_snapshot.html
+ - dom/security/test/csp/test_path_matching_redirect.html
+ - dom/security/test/csp/test_punycode_host_src.html
+ - dom/security/test/csp/test_scheme_relative_sources.html
+ - dom/security/test/csp/test_strict_dynamic_default_src.html
+ - dom/security/test/csp/test_strict_dynamic.html
+ - dom/security/test/csp/test_upgrade_insecure.html
+ - dom/security/test/csp/test_win_open_blocked.html
+ - dom/security/test/csp/test_worker_src.html
+ - dom/security/test/unit/test_isOriginPotentiallyTrustworthy.js
+ - dom/serviceworkers/test/test_error_reporting.html
+ - dom/serviceworkers/test/test_openWindow.html
+ - dom/smil/SMILTimeValueSpec.cpp
+ - dom/smil/SMILTimeValueSpec.h
+ - dom/tests/mochitest/dom-level0/idn_child.html
+ - dom/tests/mochitest/dom-level0/test_setting_document.domain_idn.html
+ - dom/tests/mochitest/whatwg/test_postMessage_origin.xhtml
+ - gfx/gl/GLContextProviderWGL.cpp
+ - gfx/gl/GLUploadHelpers.cpp
+ - gfx/ipc/GPUProcessManager.cpp
+ - gfx/layers/ipc/fuzztest/compositor_manager_parent_ipc_libfuzz.cpp
+ - gfx/tests/mochitest/test_font_whitelist.html
+ - gfx/thebes/gfxDWriteFontList.cpp
+ - gfx/thebes/gfxFcPlatformFontList.cpp
+ - gfx/thebes/gfxFT2FontList.cpp
+ - gfx/thebes/gfxPlatform.cpp
+ - gfx/thebes/gfxPlatformFontList.cpp
+ - gfx/thebes/gfxPlatformFontList.h
+ - gfx/thebes/gfxUserFontSet.cpp
+ - gfx/thebes/gfxWindowsPlatform.cpp
+ - gfx/thebes/SharedFontList.cpp
+ - gfx/vr/ipc/VRProcessManager.cpp
+ - intl/strres/nsStringBundle.cpp
+ - ipc/glue/GeckoChildProcessHost.cpp
+ - js/src/debugger/DebugAPI.h
+ - js/src/devtools/rootAnalysis/analyzeHeapWrites.js
+ - js/src/gc/Heap.h
+ - js/src/jit/CodeGenerator.cpp
+ - js/src/jit-test/tests/auto-regress/bug687399.js
+ - js/src/jit-test/tests/basic/bug908915.js
+ - js/src/jit-test/tests/basic/missingArgTest2.js
+ - js/src/tests/non262/regress/regress-450369.js
+ - js/src/tests/test262/built-ins/JSON/stringify/replacer-array-proxy.js
+ - js/xpconnect/src/Sandbox.cpp
+ - js/xpconnect/src/XPCJSRuntime.cpp
+ - js/xpconnect/src/xpcpublic.h
+ - js/xpconnect/src/XPCWrappedNativeScope.cpp
+ - js/xpconnect/tests/unit/head_watchdog.js
+ - js/xpconnect/wrappers/FilteringWrapper.cpp
+ - js/xpconnect/wrappers/XrayWrapper.cpp
+ - layout/base/PositionedEventTargeting.cpp
+ - layout/base/PresShell.cpp
+ - layout/base/PresShell.h
+ - layout/reftests/css-placeholder/css-restrictions.html
+ - layout/style/test/test_computed_style_difference.html
+ - layout/tools/reftest/mach_commands.py
+ - layout/tools/reftest/mach_test_package_commands.py
+ - layout/tools/reftest/reftestcommandline.py
+ - layout/tools/reftest/runreftest.py
+ - layout/tools/reftest/selftest/conftest.py
+ - mfbt/Attributes.h
+ - mobile/android/app/geckoview-prefs.js
+ - mobile/android/app/mobile.js
+ - mobile/android/geckoview/src/main/java/org/mozilla/gecko/util/HardwareCodecCapabilityUtils.java
+ - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/ContentBlocking.java
+ - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/CrashReporter.java
+ - mobile/android/geckoview/src/main/java/org/mozilla/geckoview/WebAuthnTokenManager.java
+ - modules/libpref/Preferences.cpp
+ - modules/libpref/docs/index.md
+ - modules/libpref/init/all.js
+ - mozglue/baseprofiler/core/platform.cpp
+ - mozglue/build/AsanOptions.cpp
+ - mozglue/misc/StackWalk.cpp
+ - netwerk/base/nsIPermissionManager.idl
+ - netwerk/base/nsIProtocolHandler.idl
+ - netwerk/base/nsIOService.cpp
+ - netwerk/base/nsIOService.h
+ - netwerk/base/nsIURI.idl
+ - netwerk/base/nsURLHelper.cpp
+ - netwerk/cookie/CookieCommons.h
+ - netwerk/dns/nsHostRecord.cpp
+ - netwerk/dns/nsIDNService.cpp
+ - netwerk/dns/nsIDNService.h
+ - netwerk/dns/nsIIDNService.idl
+ - netwerk/dns/TRR.cpp
+ - netwerk/ipc/DocumentLoadListener.cpp
+ - netwerk/protocol/about/nsAboutProtocolHandler.cpp
+ - netwerk/protocol/http/Http2Session.cpp
+ - netwerk/protocol/http/HttpBaseChannel.cpp
+ - netwerk/protocol/http/HttpConnectionMgrParent.cpp
+ - netwerk/protocol/http/HttpConnectionMgrShell.h
+ - netwerk/protocol/http/nsHttpChannel.cpp
+ - netwerk/protocol/http/nsHttpConnectionMgr.cpp
+ - netwerk/protocol/http/nsHttpHandler.cpp
+ - netwerk/protocol/http/nsHttpHandler.h
+ - netwerk/protocol/http/TRRServiceChannel.cpp
+ - netwerk/protocol/res/ExtensionProtocolHandler.cpp
+ - netwerk/protocol/viewsource/nsViewSourceChannel.cpp
+ - netwerk/protocol/websocket/BaseWebSocketChannel.cpp
+ - netwerk/socket/nsSOCKSSocketProvider.cpp
+ - netwerk/test/gtest/TestCookie.cpp
+ - netwerk/test/unit/test_bug396389.js
+ - netwerk/test/unit/test_bug427957.js
+ - netwerk/test/unit/test_bug464591.js
+ - netwerk/test/unit/test_bug479413.js
+ - netwerk/test/unit/test_cookie_blacklist.js
+ - netwerk/test/unit/test_idn_blacklist.js
+ - netwerk/test/unit/test_idn_urls.js
+ - netwerk/url-classifier/AsyncUrlChannelClassifier.cpp
+ - netwerk/url-classifier/nsChannelClassifier.cpp
+ - netwerk/url-classifier/nsChannelClassifier.h
+ - netwerk/url-classifier/UrlClassifierCommon.cpp
+ - netwerk/url-classifier/UrlClassifierCommon.h
+ - netwerk/url-classifier/UrlClassifierFeatureBase.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureBase.h
+ - netwerk/url-classifier/UrlClassifierFeatureCryptominingAnnotation.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureCryptominingProtection.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureCustomTables.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureCustomTables.h
+ - netwerk/url-classifier/UrlClassifierFeatureFactory.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureFactory.h
+ - netwerk/url-classifier/UrlClassifierFeatureFingerprintingAnnotation.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureFingerprintingProtection.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureLoginReputation.cpp
+ - netwerk/url-classifier/UrlClassifierFeaturePhishingProtection.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureSocialTrackingAnnotation.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureSocialTrackingProtection.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureTrackingAnnotation.cpp
+ - netwerk/url-classifier/UrlClassifierFeatureTrackingProtection.cpp
+ - python/mach/mach/dispatcher.py
+ - python/mozbuild/mozbuild/backend/recursivemake.py
+ - python/mozbuild/mozbuild/configure/options.py
+ - python/mozbuild/mozbuild/vendor/moz_yaml.py
+ - python/mozbuild/mozbuild/vendor/vendor_rust.py
+ - remote/cdp/Protocol.sys.mjs
+ - security/certverifier/NSSCertDBTrustDomain.cpp
+ - security/certverifier/TrustOverrideUtils.h
+ - security/manager/ssl/DataStorageList.h
+ - security/manager/ssl/nsNSSIOLayer.cpp
+ - security/manager/ssl/tests/unit/test_ev_certs.js
+ - security/manager/ssl/tests/unit/test_intermediate_preloads.js
+ - security/manager/ssl/tests/unit/test_sanctions_symantec_apple_google.js
+ - security/manager/ssl/tests/unit/tlsserver/cmd/SanctionsTestServer.cpp
+ - security/sandbox/linux/broker/SandboxBroker.cpp
+ - security/sandbox/linux/broker/SandboxBroker.h
+ - security/sandbox/linux/broker/SandboxBrokerPolicyFactory.cpp
+ - security/sandbox/linux/glue/SandboxPrefBridge.cpp
+ - security/sandbox/linux/gtest/TestBroker.cpp
+ - security/sandbox/linux/launch/SandboxLaunch.cpp
+ - security/sandbox/linux/Sandbox.cpp
+ - security/sandbox/linux/SandboxFilter.cpp
+ - security/sandbox/linux/SandboxFilterUtil.h
+ - security/sandbox/linux/Sandbox.h
+ - services/automation/ServicesAutomation.jsm
+ - services/fxaccounts/FxAccountsCommon.js
+ - services/fxaccounts/FxAccounts.jsm
+ - services/sync/modules/engines/addons.js
+ - taskcluster/ci/docker-image/kind.yml
+ - taskcluster/gecko_taskgraph/actions/create_interactive.py
+ - taskcluster/gecko_taskgraph/target_tasks.py
+ - taskcluster/gecko_taskgraph/transforms/test/other.py
+ - taskcluster/gecko_taskgraph/try_option_syntax.py
+ - taskcluster/test/test_mach_try_auto.py
+ - testing/condprofile/condprof/client.py
+ - testing/condprofile/condprof/tests/profile/prefs.js
+ - testing/condprofile/condprof/tests/test_client.py
+ - testing/firefox-ui/tests/functional/safebrowsing/test_initial_download.py
+ - testing/marionette/client/marionette_driver/wait.py
+ - testing/mochitest/browser-test.js
+ - testing/mochitest/mach_test_package_commands.py
+ - testing/mochitest/mochitest_options.py
+ - testing/mochitest/runtests.py
+ - testing/mozbase/mozprofile/mozprofile/profile.py
+ - testing/mozharness/configs/unittests/linux_unittest.py
+ - testing/mozharness/configs/unittests/mac_unittest.py
+ - testing/mozharness/configs/unittests/win_unittest.py
+ - testing/profiles/unittest-required/user.js
+ - testing/raptor/browsertime/browsertime_scenario.js
+ - testing/raptor/raptor/control_server.py
+ - testing/talos/talos/tests/devtools/addon/content/pages/custom/debugger/static/js/main.js
+ - testing/talos/talos/tests/devtools/addon/content/tests/source-map/angular-min.js.map
+ - testing/web-platform/tests/common/security-features/README.md
+ - testing/web-platform/tests/docs/writing-tests/general-guidelines.md
+ - testing/web-platform/tests/docs/writing-tests/lint-tool.md
+ - testing/web-platform/tests/tools/manifest/tests/test_manifest.py
+ - toolkit/actors/RemotePageChild.jsm
+ - toolkit/actors/WebChannelChild.jsm
+ - toolkit/components/aboutperformance/content/aboutPerformance.js
+ - toolkit/components/antitracking/StorageAccessAPIHelper.cpp
+ - toolkit/components/antitracking/PurgeTrackerService.jsm
+ - toolkit/components/antitracking/StorageAccess.cpp
+ - toolkit/components/antitracking/test/browser/antitracking_head.js
+ - toolkit/components/antitracking/test/browser/browser_siteSpecificWorkArounds.js
+ - toolkit/components/antitracking/test/browser/browser_socialtracking_save_image.js
+ - toolkit/components/antitracking/test/xpcshell/test_rejectForeignAllowList.js
+ - toolkit/components/crashes/CrashManager.in.jsm
+ - toolkit/components/crashes/tests/xpcshell/test_crash_manager.js
+ - toolkit/components/extensions/Extension.jsm
+ - toolkit/components/extensions/test/xpcshell/test_WebExtensionPolicy.js
+ - toolkit/components/formautofill/FormAutofillSync.jsm
+ - toolkit/components/remotepagemanager/RemotePageManagerParent.jsm
+ - toolkit/components/reputationservice/ApplicationReputation.cpp
+ - toolkit/components/reputationservice/chromium/chrome/common/safe_browsing/csd.pb.h
+ - toolkit/components/reputationservice/LoginReputation.cpp
+ - toolkit/components/reputationservice/LoginReputation.h
+ - toolkit/components/reputationservice/test/unit/head_download_manager.js
+ - toolkit/components/reputationservice/test/unit/test_app_rep.js
+ - toolkit/components/reputationservice/test/unit/test_app_rep_maclinux.js
+ - toolkit/components/reputationservice/test/unit/test_app_rep_windows.js
+ - toolkit/components/reputationservice/test/unit/test_login_rep.js
+ - toolkit/components/satchel/test/test_form_autocomplete.html
+ - toolkit/components/telemetry/docs/data/environment.rst
+ - toolkit/components/url-classifier/LookupCache.cpp
+ - toolkit/components/url-classifier/LookupCache.h
+ - toolkit/components/url-classifier/nsUrlClassifierDBService.cpp
+ - toolkit/components/url-classifier/nsUrlClassifierUtils.cpp
+ - toolkit/components/url-classifier/SafeBrowsing.jsm
+ - toolkit/components/url-classifier/tests/mochitest/features.js
+ - toolkit/components/url-classifier/tests/mochitest/good.js
+ - toolkit/components/url-classifier/tests/mochitest/test_annotation_vs_TP.html
+ - toolkit/components/url-classifier/tests/mochitest/test_classified_annotations.html
+ - toolkit/components/url-classifier/tests/mochitest/test_classify_by_default.html
+ - toolkit/components/url-classifier/tests/mochitest/test_classify_ping.html
+ - toolkit/components/url-classifier/tests/mochitest/test_classify_track.html
+ - toolkit/components/url-classifier/tests/mochitest/test_cryptomining_annotate.html
+ - toolkit/components/url-classifier/tests/mochitest/test_cryptomining.html
+ - toolkit/components/url-classifier/tests/mochitest/test_fingerprinting_annotate.html
+ - toolkit/components/url-classifier/tests/mochitest/test_fingerprinting.html
+ - toolkit/components/url-classifier/tests/mochitest/test_privatebrowsing_trackingprotection.html
+ - toolkit/components/url-classifier/tests/mochitest/test_safebrowsing_bug1272239.html
+ - toolkit/components/url-classifier/tests/mochitest/test_socialtracking_annotate.html
+ - toolkit/components/url-classifier/tests/mochitest/test_socialtracking.html
+ - toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1312515.html
+ - toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_bug1580416.html
+ - toolkit/components/url-classifier/tests/mochitest/test_trackingprotection_whitelist.html
+ - toolkit/components/url-classifier/tests/mochitest/trackingRequest.html
+ - toolkit/components/url-classifier/tests/unit/head_urlclassifier.js
+ - toolkit/components/url-classifier/tests/unit/test_digest256.js
+ - toolkit/components/url-classifier/tests/unit/test_features.js
+ - toolkit/components/url-classifier/tests/unit/test_platform_specific_threats.js
+ - toolkit/components/url-classifier/tests/UrlClassifierTestUtils.jsm
+ - toolkit/content/aboutSupport.js
+ - toolkit/content/aboutTelemetry.js
+ - toolkit/content/aboutUrlClassifier.js
+ - toolkit/content/aboutUrlClassifier.xhtml
+ - toolkit/content/tests/browser/browser_delay_autoplay_webAudio.js
+ - toolkit/crashreporter/client/ping.cpp
+ - toolkit/crashreporter/CrashAnnotations.cpp
+ - toolkit/crashreporter/generate_crash_reporter_sources.py
+ - toolkit/modules/PermissionsUtils.sys.mjs
+ - toolkit/modules/tests/browser/browser_AsyncPrefs.js
+ - toolkit/modules/tests/browser/browser_Troubleshoot.js
+ - toolkit/modules/tests/browser/browser_web_channel.js
+ - toolkit/modules/tests/xpcshell/test_PermissionsUtils.js
+ - toolkit/modules/third_party/jsesc/jsesc.js
+ - toolkit/modules/Troubleshoot.sys.mjs
+ - toolkit/mozapps/extensions/internal/XPIInstall.jsm
+ - toolkit/mozapps/extensions/internal/XPIProvider.jsm
+ - toolkit/mozapps/extensions/test/browser/browser_html_discover_view.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Device.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_DriverNew.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverNew.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_DriverOld.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Equal_OK.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_DriverOld.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_GTE_OK.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_No_Comparison.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OK.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OS.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_match.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_DriverVersion.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_OSVersion_mismatch_OSVersion.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_prefs.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Vendor.js
+ - toolkit/mozapps/extensions/test/xpcshell/rs-blocklist/test_gfxBlacklist_Version.js
+ - toolkit/mozapps/extensions/test/xpcshell/test_permissions.js
+ - toolkit/mozapps/extensions/test/xpcshell/test_permissions_prefs.js
+ - toolkit/mozapps/extensions/test/xpinstall/browser_bug645699.js
+ - toolkit/mozapps/extensions/test/xpinstall/browser_doorhanger_installs.js
+ - toolkit/mozapps/extensions/test/xpinstall/browser_localfile3.js
+ - toolkit/mozapps/extensions/test/xpinstall/browser_localfile4.js
+ - toolkit/mozapps/extensions/test/xpinstall/browser_localfile4_postDownload.js
+ - toolkit/mozapps/extensions/test/xpinstall/head.js
+ - toolkit/xre/nsAppRunner.cpp
+ - toolkit/xre/nsEmbedFunctions.cpp
+ - toolkit/xre/nsXREDirProvider.cpp
+ - tools/fuzzing/faulty/Faulty.cpp
+ - tools/fuzzing/faulty/Faulty.h
+ - tools/fuzzing/ipc/ProtocolFuzzer.cpp
+ - tools/fuzzing/ipc/ProtocolFuzzer.h
+ - tools/fuzzing/messagemanager/MessageManagerFuzzer.cpp
+ - tools/fuzzing/messagemanager/MessageManagerFuzzer.h
+ - tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-define-cc-etc.js
+ - tools/lint/rejected-words.yml
+ - tools/lint/test/test_codespell.py
+ - tools/profiler/core/platform.cpp
+ - tools/tryselect/selectors/chooser/__init__.py
+ - tools/tryselect/task_config.py
+ - widget/android/GfxInfo.cpp
+ - widget/GfxInfoBase.cpp
+ - widget/gtk/IMContextWrapper.cpp
+ - widget/gtk/nsAppShell.cpp
+ - widget/windows/GfxInfo.cpp
+ - widget/windows/WinUtils.cpp
+ - widget/windows/WinUtils.h
+ - xpcom/base/CycleCollectedJSRuntime.cpp
+ - xpcom/base/ErrorList.py
+ - xpcom/idl-parser/xpidl/xpidl.py
+ - xpcom/io/FilePreferences.cpp
+ - xpcom/io/FilePreferences.h
+ - xpcom/reflect/xptcall/md/unix/xptcinvoke_arm.cpp
+ - xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390.cpp
+ - xpcom/reflect/xptcall/md/unix/xptcinvoke_linux_s390x.cpp
+ - xpcom/tests/gtest/TestFilePreferencesUnix.cpp
+ - xpcom/tests/gtest/TestFilePreferencesWin.cpp
+ - xpcom/threads/nsThreadUtils.h
+
+# ---
+# Disable for now. Needs some dev to handle this
+# avoid-master-and-slave:
+# description: "Use words like 'controller', 'worker' instead"
diff --git a/tools/lint/rst.yml b/tools/lint/rst.yml
new file mode 100644
index 0000000000..3f35fe7def
--- /dev/null
+++ b/tools/lint/rst.yml
@@ -0,0 +1,11 @@
+---
+rst:
+ description: RST linter
+ include: [.]
+ extensions:
+ - rst
+ support-files:
+ - 'tools/lint/rst/**'
+ type: external
+ payload: rst:lint
+ setup: rst:setup
diff --git a/tools/lint/rst/__init__.py b/tools/lint/rst/__init__.py
new file mode 100644
index 0000000000..7151c09a59
--- /dev/null
+++ b/tools/lint/rst/__init__.py
@@ -0,0 +1,116 @@
+# 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 re
+import subprocess
+
+from mozfile import which
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+# Error Levels
+# (0, 'debug')
+# (1, 'info')
+# (2, 'warning')
+# (3, 'error')
+# (4, 'severe')
+
+abspath = os.path.abspath(os.path.dirname(__file__))
+rstcheck_requirements_file = os.path.join(abspath, "requirements.txt")
+
+results = []
+
+RSTCHECK_NOT_FOUND = """
+Could not find rstcheck! Install rstcheck and try again.
+
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ rstcheck_requirements_file
+)
+
+RSTCHECK_INSTALL_ERROR = """
+Unable to install required version of rstcheck
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ rstcheck_requirements_file
+)
+
+RSTCHECK_FORMAT_REGEX = re.compile(r"(.*):(.*): \(.*/([0-9]*)\) (.*)$")
+IGNORE_NOT_REF_LINK_UPSTREAM_BUG = re.compile(
+ r"Hyperlink target (.*) is not referenced."
+)
+
+
+def setup(root, **lintargs):
+ virtualenv_manager = lintargs["virtualenv_manager"]
+ try:
+ virtualenv_manager.install_pip_requirements(
+ rstcheck_requirements_file, quiet=True
+ )
+ except subprocess.CalledProcessError:
+ print(RSTCHECK_INSTALL_ERROR)
+ return 1
+
+
+def get_rstcheck_binary():
+ """
+ Returns the path of the first rstcheck binary available
+ if not found returns None
+ """
+ binary = os.environ.get("RSTCHECK")
+ if binary:
+ return binary
+
+ return which("rstcheck")
+
+
+def parse_with_split(errors):
+ match = RSTCHECK_FORMAT_REGEX.match(errors)
+ filename, lineno, level, message = match.groups()
+
+ return filename, lineno, level, message
+
+
+def lint(files, config, **lintargs):
+ log = lintargs["log"]
+ config["root"] = lintargs["root"]
+ paths = expand_exclusions(files, config, config["root"])
+ paths = list(paths)
+ chunk_size = 50
+ binary = get_rstcheck_binary()
+ rstcheck_options = [
+ "--ignore-language=cpp,json",
+ "--ignore-roles=searchfox",
+ ]
+
+ while paths:
+ cmdargs = [which("python"), binary] + rstcheck_options + paths[:chunk_size]
+ log.debug("Command: {}".format(" ".join(cmdargs)))
+
+ proc = subprocess.Popen(
+ cmdargs,
+ stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE,
+ env=os.environ,
+ universal_newlines=True,
+ )
+ all_errors = proc.communicate()[1]
+ for errors in all_errors.split("\n"):
+ if len(errors) > 1:
+ filename, lineno, level, message = parse_with_split(errors)
+ if not IGNORE_NOT_REF_LINK_UPSTREAM_BUG.match(message):
+ # Ignore an upstream bug
+ # https://github.com/myint/rstcheck/issues/19
+ res = {
+ "path": filename,
+ "message": message,
+ "lineno": lineno,
+ "level": "error" if int(level) >= 2 else "warning",
+ }
+ results.append(result.from_config(config, **res))
+ paths = paths[chunk_size:]
+
+ return results
diff --git a/tools/lint/rst/requirements.in b/tools/lint/rst/requirements.in
new file mode 100644
index 0000000000..e79e06c2cc
--- /dev/null
+++ b/tools/lint/rst/requirements.in
@@ -0,0 +1,2 @@
+rstcheck==3.5.0
+Pygments==2.6.1
diff --git a/tools/lint/rst/requirements.txt b/tools/lint/rst/requirements.txt
new file mode 100644
index 0000000000..e55b7bc077
--- /dev/null
+++ b/tools/lint/rst/requirements.txt
@@ -0,0 +1,17 @@
+#
+# This file is autogenerated by pip-compile
+# To update, run:
+#
+# pip-compile --generate-hashes --output-file=tools/lint/rst/requirements.txt tools/lint/rst/requirements.in
+#
+docutils==0.18 \
+ --hash=sha256:a31688b2ea858517fa54293e5d5df06fbb875fb1f7e4c64529271b77781ca8fc \
+ --hash=sha256:c1d5dab2b11d16397406a282e53953fe495a46d69ae329f55aa98a5c4e3c5fbb
+pygments==2.6.1 \
+ --hash=sha256:647344a061c249a3b74e230c739f434d7ea4d8b1d5f3721bc0f3558049b38f44 \
+ --hash=sha256:ff7a40b4860b727ab48fad6360eb351cc1b33cbf9b15a0f689ca5353e9463324 \
+ # via -r tools/lint/rst/requirements.in
+rstcheck==3.5.0 \
+ --hash=sha256:30c36768c4bd617a85ab93c31facaf410582e53803fde624845eb00c1430070c \
+ --hash=sha256:d4b035300b7d898403544f38c3a4980171ce85f487d25e188347bbafb6ee58c0
+ # via -r tools/lint/rst/requirements.in
diff --git a/tools/lint/rust/__init__.py b/tools/lint/rust/__init__.py
new file mode 100644
index 0000000000..4f63930b49
--- /dev/null
+++ b/tools/lint/rust/__init__.py
@@ -0,0 +1,180 @@
+# 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 re
+import signal
+import subprocess
+from collections import namedtuple
+
+import six
+from mozboot.util import get_tools_dir
+from mozfile import which
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+from mozprocess import ProcessHandler
+from packaging.version import Version
+
+RUSTFMT_NOT_FOUND = """
+Could not find rustfmt! Install rustfmt and try again.
+
+ $ rustup component add rustfmt
+
+And make sure that it is in the PATH
+""".strip()
+
+
+RUSTFMT_INSTALL_ERROR = """
+Unable to install correct version of rustfmt
+Try to install it manually with:
+ $ rustup component add rustfmt
+""".strip()
+
+
+RUSTFMT_WRONG_VERSION = """
+You are probably using an old version of rustfmt.
+Expected version is {version}.
+Try to update it:
+ $ rustup update stable
+""".strip()
+
+
+def parse_issues(config, output, paths):
+ RustfmtDiff = namedtuple("RustfmtDiff", ["file", "line", "diff"])
+ issues = []
+ diff_line = re.compile("^Diff in (.*) at line ([0-9]*):")
+ file = ""
+ line_no = 0
+ diff = ""
+ for line in output:
+ line = six.ensure_text(line)
+ match = diff_line.match(line)
+ if match:
+ if diff:
+ issues.append(RustfmtDiff(file, line_no, diff.rstrip("\n")))
+ diff = ""
+ file, line_no = match.groups()
+ else:
+ diff += line + "\n"
+ # the algorithm above will always skip adding the last issue
+ issues.append(RustfmtDiff(file, line_no, diff))
+ file = os.path.normcase(os.path.normpath(file))
+ results = []
+ for issue in issues:
+ # rustfmt can not be supplied the paths to the files we want to analyze
+ # therefore, for each issue detected, we check if any of the the paths
+ # supplied are part of the file name.
+ # This just filters out the issues that are not part of paths.
+ if any([os.path.normcase(os.path.normpath(path)) in file for path in paths]):
+ res = {
+ "path": issue.file,
+ "diff": issue.diff,
+ "level": "warning",
+ "lineno": issue.line,
+ }
+ results.append(result.from_config(config, **res))
+ return {"results": results, "fixed": 0}
+
+
+class RustfmtProcess(ProcessHandler):
+ def __init__(self, config, *args, **kwargs):
+ self.config = config
+ kwargs["stream"] = False
+ ProcessHandler.__init__(self, *args, **kwargs)
+
+ def run(self, *args, **kwargs):
+ orig = signal.signal(signal.SIGINT, signal.SIG_IGN)
+ ProcessHandler.run(self, *args, **kwargs)
+ signal.signal(signal.SIGINT, orig)
+
+
+def run_process(config, cmd):
+ proc = RustfmtProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+ return proc.output
+
+
+def get_rustfmt_binary():
+ """
+ Returns the path of the first rustfmt binary available
+ if not found returns None
+ """
+ binary = os.environ.get("RUSTFMT")
+ if binary:
+ return binary
+
+ rust_path = os.path.join(get_tools_dir(), "rustc", "bin")
+ return which("rustfmt", path=os.pathsep.join([rust_path, os.environ["PATH"]]))
+
+
+def get_rustfmt_version(binary):
+ """
+ Returns found binary's version
+ """
+ try:
+ output = subprocess.check_output(
+ [binary, "--version"],
+ stderr=subprocess.STDOUT,
+ universal_newlines=True,
+ )
+ except subprocess.CalledProcessError as e:
+ output = e.output
+
+ version = re.findall(r"\d.\d+.\d+", output)[0]
+ return Version(version)
+
+
+def lint(paths, config, fix=None, **lintargs):
+ log = lintargs["log"]
+ paths = list(expand_exclusions(paths, config, lintargs["root"]))
+
+ # An empty path array can occur when the user passes in `-n`. If we don't
+ # return early in this case, rustfmt will attempt to read stdin and hang.
+ if not paths:
+ return []
+
+ binary = get_rustfmt_binary()
+
+ if not binary:
+ print(RUSTFMT_NOT_FOUND)
+ if "MOZ_AUTOMATION" in os.environ:
+ return 1
+ return []
+
+ min_version_str = config.get("min_rustfmt_version")
+ min_version = Version(min_version_str)
+ actual_version = get_rustfmt_version(binary)
+ log.debug(
+ "Found version: {}. Minimal expected version: {}".format(
+ actual_version, min_version
+ )
+ )
+
+ if actual_version < min_version:
+ print(RUSTFMT_WRONG_VERSION.format(version=min_version_str))
+ return 1
+
+ cmd_args = [binary]
+ cmd_args.append("--check")
+ base_command = cmd_args + paths
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ output = run_process(config, base_command)
+
+ issues = parse_issues(config, output, paths)
+
+ if fix:
+ issues["fixed"] = len(issues["results"])
+ issues["results"] = []
+ cmd_args.remove("--check")
+
+ base_command = cmd_args + paths
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ output = run_process(config, base_command)
+
+ return issues
diff --git a/tools/lint/rustfmt.yml b/tools/lint/rustfmt.yml
new file mode 100644
index 0000000000..4fcd952a9b
--- /dev/null
+++ b/tools/lint/rustfmt.yml
@@ -0,0 +1,20 @@
+---
+rust:
+ description: Reformat rust
+ min_rustfmt_version: 1.4.12
+ include:
+ - '.'
+ exclude:
+ - dom/webauthn/libudev-sys/
+ - gfx/wr/peek-poke/
+ - gfx/wr/webrender_build/
+ - gfx/wr/wr_malloc_size_of/
+ - media/mp4parse-rust/
+ - servo/
+ - '**/*.mako.rs'
+ extensions:
+ - rs
+ support-files:
+ - 'tools/lint/rust/**'
+ type: external
+ payload: rust:lint
diff --git a/tools/lint/shell/__init__.py b/tools/lint/shell/__init__.py
new file mode 100644
index 0000000000..b75dc2d159
--- /dev/null
+++ b/tools/lint/shell/__init__.py
@@ -0,0 +1,148 @@
+# 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 json
+import os
+from json.decoder import JSONDecodeError
+
+import mozpack.path as mozpath
+from mozfile import which
+from mozlint import result
+from mozlint.util.implementation import LintProcess
+from mozpack.files import FileFinder
+
+SHELLCHECK_NOT_FOUND = """
+Unable to locate shellcheck, please ensure it is installed and in
+your PATH or set the SHELLCHECK environment variable.
+
+https://shellcheck.net or your system's package manager.
+""".strip()
+
+results = []
+
+
+class ShellcheckProcess(LintProcess):
+ def process_line(self, line):
+ try:
+ data = json.loads(line)
+ except JSONDecodeError as e:
+ print("Unable to load shellcheck output ({}): {}".format(e, line))
+ return
+
+ for entry in data:
+ res = {
+ "path": entry["file"],
+ "message": entry["message"],
+ "level": "error",
+ "lineno": entry["line"],
+ "column": entry["column"],
+ "rule": entry["code"],
+ }
+ results.append(result.from_config(self.config, **res))
+
+
+def determine_shell_from_script(path):
+ """Returns a string identifying the shell used.
+
+ Returns None if not identifiable.
+
+ Copes with the following styles:
+ #!bash
+ #!/bin/bash
+ #!/usr/bin/env bash
+ """
+ with open(path, "r") as f:
+ head = f.readline()
+
+ if not head.startswith("#!"):
+ return
+
+ # allow for parameters to the shell
+ shebang = head.split()[0]
+
+ # if the first entry is a variant of /usr/bin/env
+ if "env" in shebang:
+ shebang = head.split()[1]
+
+ if shebang.endswith("sh"):
+ # Strip first to avoid issues with #!bash
+ return shebang.strip("#!").split("/")[-1]
+ # make it clear we return None, rather than fall through.
+ return
+
+
+def find_shell_scripts(config, paths):
+ found = dict()
+
+ root = config["root"]
+ exclude = [mozpath.join(root, e) for e in config.get("exclude", [])]
+
+ if config.get("extensions"):
+ pattern = "**/*.{}".format(config.get("extensions")[0])
+ else:
+ pattern = "**/*.sh"
+
+ files = []
+ for path in paths:
+ path = mozpath.normsep(path)
+ ignore = [
+ e[len(path) :].lstrip("/")
+ for e in exclude
+ if mozpath.commonprefix((path, e)) == path
+ ]
+ finder = FileFinder(path, ignore=ignore)
+ files.extend([os.path.join(path, p) for p, f in finder.find(pattern)])
+
+ for filename in files:
+ shell = determine_shell_from_script(filename)
+ if shell:
+ found[filename] = shell
+ return found
+
+
+def run_process(config, cmd):
+ proc = ShellcheckProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+
+def get_shellcheck_binary():
+ """
+ Returns the path of the first shellcheck binary available
+ if not found returns None
+ """
+ binary = os.environ.get("SHELLCHECK")
+ if binary:
+ return binary
+
+ return which("shellcheck")
+
+
+def lint(paths, config, **lintargs):
+ log = lintargs["log"]
+ binary = get_shellcheck_binary()
+
+ if not binary:
+ print(SHELLCHECK_NOT_FOUND)
+ if "MOZ_AUTOMATION" in os.environ:
+ return 1
+ return []
+
+ config["root"] = lintargs["root"]
+
+ files = find_shell_scripts(config, paths)
+
+ base_command = [binary, "-f", "json"]
+ if config.get("excludecodes"):
+ base_command.extend(["-e", ",".join(config.get("excludecodes"))])
+
+ for f in files:
+ cmd = list(base_command)
+ cmd.extend(["-s", files[f], f])
+ log.debug("Command: {}".format(cmd))
+ run_process(config, cmd)
+ return results
diff --git a/tools/lint/shellcheck.yml b/tools/lint/shellcheck.yml
new file mode 100644
index 0000000000..0100e3d5cc
--- /dev/null
+++ b/tools/lint/shellcheck.yml
@@ -0,0 +1,15 @@
+---
+shellcheck:
+ description: Shell script linter
+ include:
+ - extensions/spellcheck/locales/en-US/hunspell/dictionary-sources/
+ - taskcluster/docker/
+ exclude: []
+ # 1090: https://github.com/koalaman/shellcheck/wiki/SC1090
+ # 'Can't follow a non-constant source'
+ extensions: ['sh']
+ support-files:
+ - 'tools/lint/shell/**'
+ excludecodes: ['1090', '1091']
+ type: external
+ payload: shell:lint
diff --git a/tools/lint/spell/__init__.py b/tools/lint/spell/__init__.py
new file mode 100644
index 0000000000..65712acdd7
--- /dev/null
+++ b/tools/lint/spell/__init__.py
@@ -0,0 +1,168 @@
+# 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 re
+import subprocess
+
+# py2-compat
+try:
+ from json.decoder import JSONDecodeError
+except ImportError:
+ JSONDecodeError = ValueError
+
+from mozfile import which
+from mozlint import result
+from mozlint.util.implementation import LintProcess
+
+here = os.path.abspath(os.path.dirname(__file__))
+CODESPELL_REQUIREMENTS_PATH = os.path.join(here, "codespell_requirements.txt")
+
+CODESPELL_NOT_FOUND = """
+Could not find codespell! Install codespell and try again.
+
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ CODESPELL_REQUIREMENTS_PATH
+)
+
+
+CODESPELL_INSTALL_ERROR = """
+Unable to install correct version of codespell
+Try to install it manually with:
+ $ pip install -U --require-hashes -r {}
+""".strip().format(
+ CODESPELL_REQUIREMENTS_PATH
+)
+
+results = []
+
+CODESPELL_FORMAT_REGEX = re.compile(r"(.*):(.*): (.*) ==> (.*)$")
+
+
+class CodespellProcess(LintProcess):
+ fixed = 0
+ _fix = None
+
+ def process_line(self, line):
+ try:
+ match = CODESPELL_FORMAT_REGEX.match(line)
+ abspath, line, typo, correct = match.groups()
+ except AttributeError:
+ if "FIXED: " not in line:
+ print("Unable to match regex against output: {}".format(line))
+ return
+
+ if CodespellProcess._fix:
+ CodespellProcess.fixed += 1
+
+ # Ignore false positive like aParent (which would be fixed to apparent)
+ # See https://github.com/lucasdemarchi/codespell/issues/314
+ m = re.match(r"^[a-z][A-Z][a-z]*", typo)
+ if m:
+ return
+ res = {
+ "path": abspath,
+ "message": typo.strip() + " ==> " + correct,
+ "level": "error",
+ "lineno": line,
+ }
+ results.append(result.from_config(self.config, **res))
+
+
+def run_process(config, cmd):
+ proc = CodespellProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+
+def get_codespell_binary():
+ """
+ Returns the path of the first codespell binary available
+ if not found returns None
+ """
+ binary = os.environ.get("CODESPELL")
+ if binary:
+ return binary
+
+ return which("codespell")
+
+
+def setup(root, **lintargs):
+ virtualenv_manager = lintargs["virtualenv_manager"]
+ try:
+ virtualenv_manager.install_pip_requirements(
+ CODESPELL_REQUIREMENTS_PATH, quiet=True
+ )
+ except subprocess.CalledProcessError:
+ print(CODESPELL_INSTALL_ERROR)
+ return 1
+
+
+def get_codespell_version(binary):
+ return subprocess.check_output(
+ [which("python"), binary, "--version"],
+ universal_newlines=True,
+ stderr=subprocess.STDOUT,
+ )
+
+
+def get_ignored_words_file(config):
+ config_root = os.path.dirname(config["path"])
+ return os.path.join(config_root, "spell", "exclude-list.txt")
+
+
+def lint(paths, config, fix=None, **lintargs):
+ log = lintargs["log"]
+ binary = get_codespell_binary()
+ if not binary:
+ print(CODESPELL_NOT_FOUND)
+ if "MOZ_AUTOMATION" in os.environ:
+ return 1
+ return []
+
+ config["root"] = lintargs["root"]
+
+ exclude_list = get_ignored_words_file(config)
+ cmd_args = [
+ which("python"),
+ binary,
+ "--disable-colors",
+ # Silence some warnings:
+ # 1: disable warnings about wrong encoding
+ # 2: disable warnings about binary file
+ # 4: shut down warnings about automatic fixes
+ # that were disabled in dictionary.
+ "--quiet-level=7",
+ "--ignore-words=" + exclude_list,
+ ]
+
+ if "exclude" in config:
+ cmd_args.append("--skip=*.dic,{}".format(",".join(config["exclude"])))
+
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ log.debug("Version: {}".format(get_codespell_version(binary)))
+
+ if fix:
+ CodespellProcess._fix = True
+
+ base_command = cmd_args + paths
+ run_process(config, base_command)
+
+ if fix:
+ global results
+ results = []
+ cmd_args.append("--write-changes")
+ log.debug("Command: {}".format(" ".join(cmd_args)))
+ log.debug("Version: {}".format(get_codespell_version(binary)))
+ base_command = cmd_args + paths
+ run_process(config, base_command)
+ CodespellProcess.fixed = CodespellProcess.fixed - len(results)
+ else:
+ CodespellProcess.fixed = 0
+
+ return {"results": results, "fixed": CodespellProcess.fixed}
diff --git a/tools/lint/spell/codespell_requirements.in b/tools/lint/spell/codespell_requirements.in
new file mode 100644
index 0000000000..7c1c2ac745
--- /dev/null
+++ b/tools/lint/spell/codespell_requirements.in
@@ -0,0 +1 @@
+codespell==2.2.2
diff --git a/tools/lint/spell/codespell_requirements.txt b/tools/lint/spell/codespell_requirements.txt
new file mode 100644
index 0000000000..b360e3cfa8
--- /dev/null
+++ b/tools/lint/spell/codespell_requirements.txt
@@ -0,0 +1,10 @@
+#
+# This file is autogenerated by pip-compile with python 3.10
+# To update, run:
+#
+# pip-compile --generate-hashes tools/lint/spell/codespell_requirements.in
+#
+codespell==2.2.2 \
+ --hash=sha256:87dfcd9bdc9b3cb8b067b37f0af22044d7a84e28174adfc8eaa203056b7f9ecc \
+ --hash=sha256:c4d00c02b5a2a55661f00d5b4b3b5a710fa803ced9a9d7e45438268b099c319c
+ # via -r tools/lint/spell/codespell_requirements.in
diff --git a/tools/lint/spell/exclude-list.txt b/tools/lint/spell/exclude-list.txt
new file mode 100644
index 0000000000..db7710a3b1
--- /dev/null
+++ b/tools/lint/spell/exclude-list.txt
@@ -0,0 +1,23 @@
+cas
+optin
+aparent
+acount
+te
+wasn
+incrementall
+aare
+whats
+crate
+files'
+thru
+referer
+dur
+ue
+tring
+delink
+warmup
+aNumber
+falsy
+rduce
+complies
+
diff --git a/tools/lint/test-manifest-disable.yml b/tools/lint/test-manifest-disable.yml
new file mode 100644
index 0000000000..7d93889704
--- /dev/null
+++ b/tools/lint/test-manifest-disable.yml
@@ -0,0 +1,16 @@
+---
+no-comment-disable:
+ description: >
+ "Use 'disabled=<reason>' to disable a test instead of a
+ comment"
+ include: ['.']
+ exclude:
+ - "**/application.ini"
+ - "**/l10n.ini"
+ - dom/canvas/test/webgl-conf/mochitest-errata.ini
+ - testing/mozbase/manifestparser/tests
+ - testing/web-platform
+ - xpcom/tests/unit/data
+ extensions: ['ini']
+ type: regex
+ payload: ^[ \t]*(#|;)[ \t]*\[
diff --git a/tools/lint/test-manifest-skip-if.yml b/tools/lint/test-manifest-skip-if.yml
new file mode 100644
index 0000000000..7ad1bd717c
--- /dev/null
+++ b/tools/lint/test-manifest-skip-if.yml
@@ -0,0 +1,18 @@
+---
+multiline-skip-if:
+ description: Conditions joined by || should be on separate lines
+ hint: |
+ skip-if =
+ <condition one> # reason
+ <condition two> # reason
+ exclude:
+ - "**/application.ini"
+ - "**/l10n.ini"
+ - dom/canvas/test/webgl-conf/mochitest-errata.ini
+ - testing/mozbase/manifestparser/tests
+ - testing/web-platform
+ - xpcom/tests/unit/data
+ extensions: ['ini']
+ level: warning
+ type: regex
+ payload: '^\s*(skip|fail)-if\s*=[^(]*\|\|'
diff --git a/tools/lint/test/conftest.py b/tools/lint/test/conftest.py
new file mode 100644
index 0000000000..ca5adab7ae
--- /dev/null
+++ b/tools/lint/test/conftest.py
@@ -0,0 +1,301 @@
+import logging
+import os
+import pathlib
+import sys
+from collections import defaultdict
+
+import pytest
+from mozbuild.base import MozbuildObject
+from mozlint.parser import Parser
+from mozlint.pathutils import findobject
+from mozlint.result import ResultSummary
+from mozlog.structuredlog import StructuredLogger
+from mozpack import path
+
+here = path.abspath(path.dirname(__file__))
+build = MozbuildObject.from_environment(cwd=here, virtualenv_name="python-test")
+
+lintdir = path.dirname(here)
+sys.path.insert(0, lintdir)
+logger = logging.getLogger("mozlint")
+
+
+def pytest_generate_tests(metafunc):
+ """Finds, loads and returns the config for the linter name specified by the
+ LINTER global variable in the calling module.
+
+ This implies that each test file (that uses this fixture) should only be
+ used to test a single linter. If no LINTER variable is defined, the test
+ will fail.
+ """
+ if "config" in metafunc.fixturenames:
+ if not hasattr(metafunc.module, "LINTER"):
+ pytest.fail(
+ "'config' fixture used from a module that didn't set the LINTER variable"
+ )
+
+ name = metafunc.module.LINTER
+ config_path = path.join(lintdir, "{}.yml".format(name))
+ parser = Parser(build.topsrcdir)
+ configs = parser.parse(config_path)
+ config_names = {config["name"] for config in configs}
+
+ marker = metafunc.definition.get_closest_marker("lint_config")
+ if marker:
+ config_name = marker.kwargs["name"]
+ if config_name not in config_names:
+ pytest.fail(f"lint config {config_name} not present in {name}.yml")
+ configs = [
+ config for config in configs if config["name"] == marker.kwargs["name"]
+ ]
+
+ ids = [config["name"] for config in configs]
+ metafunc.parametrize("config", configs, ids=ids)
+
+
+@pytest.fixture(scope="module")
+def root(request):
+ """Return the root directory for the files of the linter under test.
+
+ For example, with LINTER=flake8 this would be tools/lint/test/files/flake8.
+ """
+ if not hasattr(request.module, "LINTER"):
+ pytest.fail(
+ "'root' fixture used from a module that didn't set the LINTER variable"
+ )
+ return path.join(here, "files", request.module.LINTER)
+
+
+@pytest.fixture(scope="module")
+def paths(root):
+ """Return a function that can resolve file paths relative to the linter
+ under test.
+
+ Can be used like `paths('foo.py', 'bar/baz')`. This will return a list of
+ absolute paths under the `root` files directory.
+ """
+
+ def _inner(*paths):
+ if not paths:
+ return [root]
+ return [path.normpath(path.join(root, p)) for p in paths]
+
+ return _inner
+
+
+@pytest.fixture(autouse=True)
+def run_setup(config):
+ """Make sure that if the linter named in the LINTER global variable has a
+ setup function, it gets called before running the tests.
+ """
+ if "setup" not in config:
+ return
+
+ if config["name"] == "clang-format":
+ # Skip the setup for the clang-format linter, as it requires a Mach context
+ # (which we may not have if pytest is invoked directly).
+ return
+
+ log = logging.LoggerAdapter(
+ logger, {"lintname": config.get("name"), "pid": os.getpid()}
+ )
+
+ func = findobject(config["setup"])
+ func(
+ build.topsrcdir,
+ virtualenv_manager=build.virtualenv_manager,
+ virtualenv_bin_path=build.virtualenv_manager.bin_path,
+ log=log,
+ )
+
+
+@pytest.fixture
+def lint(config, root, request):
+ """Find and return the 'lint' function for the external linter named in the
+ LINTER global variable.
+
+ This will automatically pass in the 'config' and 'root' arguments if not
+ specified.
+ """
+ try:
+ func = findobject(config["payload"])
+ except (ImportError, ValueError):
+ pytest.fail(
+ "could not resolve a lint function from '{}'".format(config["payload"])
+ )
+
+ ResultSummary.root = root
+
+ def wrapper(paths, config=config, root=root, collapse_results=False, **lintargs):
+ logger.setLevel(logging.DEBUG)
+ lintargs["log"] = logging.LoggerAdapter(
+ logger, {"lintname": config.get("name"), "pid": os.getpid()}
+ )
+
+ results = func(paths, config, root=root, **lintargs)
+ if hasattr(request.module, "fixed") and isinstance(results, dict):
+ request.module.fixed += results["fixed"]
+
+ if isinstance(results, dict):
+ results = results["results"]
+
+ if isinstance(results, (list, tuple)):
+ results = sorted(results)
+
+ if not collapse_results:
+ return results
+
+ ret = defaultdict(list)
+ for r in results:
+ ret[r.relpath].append(r)
+ return ret
+
+ return wrapper
+
+
+@pytest.fixture
+def structuredlog_lint(config, root, logger=None):
+ """Find and return the 'lint' function for the external linter named in the
+ LINTER global variable. This variant of the lint function is for linters that
+ use the 'structuredlog' type.
+
+ This will automatically pass in the 'config' and 'root' arguments if not
+ specified.
+ """
+ try:
+ func = findobject(config["payload"])
+ except (ImportError, ValueError):
+ pytest.fail(
+ "could not resolve a lint function from '{}'".format(config["payload"])
+ )
+
+ ResultSummary.root = root
+
+ if not logger:
+ logger = structured_logger()
+
+ def wrapper(
+ paths,
+ config=config,
+ root=root,
+ logger=logger,
+ collapse_results=False,
+ **lintargs,
+ ):
+ lintargs["log"] = logging.LoggerAdapter(
+ logger, {"lintname": config.get("name"), "pid": os.getpid()}
+ )
+ results = func(paths, config, root=root, logger=logger, **lintargs)
+ if not collapse_results:
+ return results
+
+ ret = defaultdict(list)
+ for r in results:
+ ret[r.path].append(r)
+ return ret
+
+ return wrapper
+
+
+@pytest.fixture
+def global_lint(config, root, request):
+ try:
+ func = findobject(config["payload"])
+ except (ImportError, ValueError):
+ pytest.fail(
+ "could not resolve a lint function from '{}'".format(config["payload"])
+ )
+
+ ResultSummary.root = root
+
+ def wrapper(config=config, root=root, collapse_results=False, **lintargs):
+ logger.setLevel(logging.DEBUG)
+ lintargs["log"] = logging.LoggerAdapter(
+ logger, {"lintname": config.get("name"), "pid": os.getpid()}
+ )
+ results = func(config, root=root, **lintargs)
+ if hasattr(request.module, "fixed") and isinstance(results, dict):
+ request.module.fixed += results["fixed"]
+
+ if isinstance(results, dict):
+ results = results["results"]
+
+ if isinstance(results, (list, tuple)):
+ results = sorted(results)
+
+ if not collapse_results:
+ return results
+
+ ret = defaultdict(list)
+ for r in results:
+ ret[r.relpath].append(r)
+ return ret
+
+ return wrapper
+
+
+@pytest.fixture
+def create_temp_file(tmpdir):
+ def inner(contents, name=None):
+ name = name or "temp.py"
+ path = tmpdir.join(name)
+ path.write(contents)
+ return path.strpath
+
+ return inner
+
+
+@pytest.fixture
+def structured_logger():
+ return StructuredLogger("logger")
+
+
+@pytest.fixture
+def perfdocs_sample():
+ from test_perfdocs import (
+ DYNAMIC_SAMPLE_CONFIG,
+ SAMPLE_CONFIG,
+ SAMPLE_INI,
+ SAMPLE_TEST,
+ temp_dir,
+ temp_file,
+ )
+
+ with temp_dir() as tmpdir:
+ suite_dir = pathlib.Path(tmpdir, "suite")
+ raptor_dir = pathlib.Path(tmpdir, "raptor")
+ raptor_suitedir = pathlib.Path(tmpdir, "raptor", "suite")
+ raptor_another_suitedir = pathlib.Path(tmpdir, "raptor", "another_suite")
+ perfdocs_dir = pathlib.Path(tmpdir, "perfdocs")
+
+ perfdocs_dir.mkdir(parents=True, exist_ok=True)
+ suite_dir.mkdir(parents=True, exist_ok=True)
+ raptor_dir.mkdir(parents=True, exist_ok=True)
+ raptor_suitedir.mkdir(parents=True, exist_ok=True)
+ raptor_another_suitedir.mkdir(parents=True, exist_ok=True)
+
+ with temp_file(
+ "perftest.ini", tempdir=suite_dir, content="[perftest_sample.js]"
+ ) as tmpmanifest, temp_file(
+ "raptor_example1.ini", tempdir=raptor_suitedir, content=SAMPLE_INI
+ ) as tmpexample1manifest, temp_file(
+ "raptor_example2.ini", tempdir=raptor_another_suitedir, content=SAMPLE_INI
+ ) as tmpexample2manifest, temp_file(
+ "perftest_sample.js", tempdir=suite_dir, content=SAMPLE_TEST
+ ) as tmptest, temp_file(
+ "config.yml", tempdir=perfdocs_dir, content=SAMPLE_CONFIG
+ ) as tmpconfig, temp_file(
+ "config_2.yml", tempdir=perfdocs_dir, content=DYNAMIC_SAMPLE_CONFIG
+ ) as tmpconfig_2, temp_file(
+ "index.rst", tempdir=perfdocs_dir, content="{documentation}"
+ ) as tmpindex:
+ yield {
+ "top_dir": tmpdir,
+ "manifest": {"path": tmpmanifest},
+ "example1_manifest": tmpexample1manifest,
+ "example2_manifest": tmpexample2manifest,
+ "test": tmptest,
+ "config": tmpconfig,
+ "config_2": tmpconfig_2,
+ "index": tmpindex,
+ }
diff --git a/tools/lint/test/files/android-format/Bad.java b/tools/lint/test/files/android-format/Bad.java
new file mode 100644
index 0000000000..13aa5d49d5
--- /dev/null
+++ b/tools/lint/test/files/android-format/Bad.java
@@ -0,0 +1,8 @@
+package org.mozilla.geckoview;
+
+import java.util.Arrays;
+
+public class Bad {
+ public static void main() {
+ }
+}
diff --git a/tools/lint/test/files/android-format/Main.kt b/tools/lint/test/files/android-format/Main.kt
new file mode 100644
index 0000000000..a172cf71ee
--- /dev/null
+++ b/tools/lint/test/files/android-format/Main.kt
@@ -0,0 +1,7 @@
+package org.mozilla.geckoview
+
+import java.util.Arrays;
+
+fun main() {
+println("Hello")
+}
diff --git a/tools/lint/test/files/android-format/build.gradle b/tools/lint/test/files/android-format/build.gradle
new file mode 100644
index 0000000000..6d2aff6d60
--- /dev/null
+++ b/tools/lint/test/files/android-format/build.gradle
@@ -0,0 +1 @@
+buildDir "${topobjdir}/gradle/build/tools/lint/test/files/android-format"
diff --git a/tools/lint/test/files/black/bad.py b/tools/lint/test/files/black/bad.py
new file mode 100644
index 0000000000..0a50df4dd9
--- /dev/null
+++ b/tools/lint/test/files/black/bad.py
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+print (
+ "test"
+ )
diff --git a/tools/lint/test/files/black/invalid.py b/tools/lint/test/files/black/invalid.py
new file mode 100644
index 0000000000..079ecbad30
--- /dev/null
+++ b/tools/lint/test/files/black/invalid.py
@@ -0,0 +1,4 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+print(
diff --git a/tools/lint/test/files/clang-format/bad/bad.cpp b/tools/lint/test/files/clang-format/bad/bad.cpp
new file mode 100644
index 0000000000..f08a83f795
--- /dev/null
+++ b/tools/lint/test/files/clang-format/bad/bad.cpp
@@ -0,0 +1,6 @@
+int main ( ) {
+
+return 0;
+
+
+}
diff --git a/tools/lint/test/files/clang-format/bad/bad2.c b/tools/lint/test/files/clang-format/bad/bad2.c
new file mode 100644
index 0000000000..9792e85071
--- /dev/null
+++ b/tools/lint/test/files/clang-format/bad/bad2.c
@@ -0,0 +1,8 @@
+#include "bad2.h"
+
+
+int bad2() {
+ int a =2;
+ return a;
+
+}
diff --git a/tools/lint/test/files/clang-format/bad/bad2.h b/tools/lint/test/files/clang-format/bad/bad2.h
new file mode 100644
index 0000000000..a35d49d7e7
--- /dev/null
+++ b/tools/lint/test/files/clang-format/bad/bad2.h
@@ -0,0 +1 @@
+int bad2(void );
diff --git a/tools/lint/test/files/clang-format/bad/good.cpp b/tools/lint/test/files/clang-format/bad/good.cpp
new file mode 100644
index 0000000000..76e8197013
--- /dev/null
+++ b/tools/lint/test/files/clang-format/bad/good.cpp
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tools/lint/test/files/clang-format/good/foo.cpp b/tools/lint/test/files/clang-format/good/foo.cpp
new file mode 100644
index 0000000000..76e8197013
--- /dev/null
+++ b/tools/lint/test/files/clang-format/good/foo.cpp
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tools/lint/test/files/clippy/test1/Cargo.toml b/tools/lint/test/files/clippy/test1/Cargo.toml
new file mode 100644
index 0000000000..92d5072eca
--- /dev/null
+++ b/tools/lint/test/files/clippy/test1/Cargo.toml
@@ -0,0 +1,17 @@
+[package]
+name = "hello_world" # the name of the package
+version = "0.1.0" # the current version, obeying semver
+authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
+
+[[bin]]
+name ="good"
+path = "good.rs"
+
+[[bin]]
+name ="bad"
+path = "bad.rs"
+
+[[bin]]
+name ="bad2"
+path = "bad2.rs"
+
diff --git a/tools/lint/test/files/clippy/test1/bad.rs b/tools/lint/test/files/clippy/test1/bad.rs
new file mode 100644
index 0000000000..c403fba603
--- /dev/null
+++ b/tools/lint/test/files/clippy/test1/bad.rs
@@ -0,0 +1,14 @@
+fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+ // Clippy detects this as a swap and considers this as an error
+ let mut a=1;
+ let mut b=1;
+
+ a = b;
+ b = a;
+
+
+}
diff --git a/tools/lint/test/files/clippy/test1/bad2.rs b/tools/lint/test/files/clippy/test1/bad2.rs
new file mode 100644
index 0000000000..bf488bbe72
--- /dev/null
+++ b/tools/lint/test/files/clippy/test1/bad2.rs
@@ -0,0 +1,14 @@
+fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+ let mut a;
+ let mut b=1;
+ let mut vec = vec![1, 2];
+
+ for x in 5..10 - 5 {
+ a = x;
+ }
+
+ }
diff --git a/tools/lint/test/files/clippy/test1/good.rs b/tools/lint/test/files/clippy/test1/good.rs
new file mode 100644
index 0000000000..9bcaee67b7
--- /dev/null
+++ b/tools/lint/test/files/clippy/test1/good.rs
@@ -0,0 +1,6 @@
+fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+}
diff --git a/tools/lint/test/files/clippy/test2/Cargo.lock b/tools/lint/test/files/clippy/test2/Cargo.lock
new file mode 100644
index 0000000000..6b2bc69eeb
--- /dev/null
+++ b/tools/lint/test/files/clippy/test2/Cargo.lock
@@ -0,0 +1,5 @@
+# This file is automatically @generated by Cargo.
+# It is not intended for manual editing.
+[[package]]
+name = "hello_world_2"
+version = "0.2.0"
diff --git a/tools/lint/test/files/clippy/test2/Cargo.toml b/tools/lint/test/files/clippy/test2/Cargo.toml
new file mode 100644
index 0000000000..b0ac992088
--- /dev/null
+++ b/tools/lint/test/files/clippy/test2/Cargo.toml
@@ -0,0 +1,8 @@
+[package]
+name = "hello_world_2" # the name of the package
+version = "0.2.0" # the current version, obeying semver
+authors = ["Alice <a@example.com>", "Bob <b@example.com>"]
+
+[[bin]]
+name = "fake_lib1"
+path = "src/bad_1.rs"
diff --git a/tools/lint/test/files/clippy/test2/src/bad_1.rs b/tools/lint/test/files/clippy/test2/src/bad_1.rs
new file mode 100644
index 0000000000..2fe0630202
--- /dev/null
+++ b/tools/lint/test/files/clippy/test2/src/bad_1.rs
@@ -0,0 +1,15 @@
+mod bad_2;
+
+fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+ // Clippy detects this as a swap and considers this as an error
+ let mut a=1;
+ let mut b=1;
+
+ a = b;
+ b = a;
+
+}
diff --git a/tools/lint/test/files/clippy/test2/src/bad_2.rs b/tools/lint/test/files/clippy/test2/src/bad_2.rs
new file mode 100644
index 0000000000..f77de330b4
--- /dev/null
+++ b/tools/lint/test/files/clippy/test2/src/bad_2.rs
@@ -0,0 +1,17 @@
+fn foo() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+ let mut a;
+ let mut b=1;
+ let mut vec = Vec::new();
+ vec.push(1);
+ vec.push(2);
+
+
+ for x in 5..10 - 5 {
+ a = x;
+ }
+
+ }
diff --git a/tools/lint/test/files/codespell/ignore.rst b/tools/lint/test/files/codespell/ignore.rst
new file mode 100644
index 0000000000..1371d07054
--- /dev/null
+++ b/tools/lint/test/files/codespell/ignore.rst
@@ -0,0 +1,5 @@
+This is a file with some typos and informations.
+But also testing false positive like optin (because this isn't always option)
+or stuff related to our coding style like:
+aparent (aParent).
+but detects mistakes like mozila
diff --git a/tools/lint/test/files/eslint/good.js b/tools/lint/test/files/eslint/good.js
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tools/lint/test/files/eslint/good.js
diff --git a/tools/lint/test/files/eslint/import/bad_import.js b/tools/lint/test/files/eslint/import/bad_import.js
new file mode 100644
index 0000000000..e2a8ec8de1
--- /dev/null
+++ b/tools/lint/test/files/eslint/import/bad_import.js
@@ -0,0 +1 @@
+/* import-globals-from notpresent/notpresent.js */
diff --git a/tools/lint/test/files/eslint/nolint/foo.txt b/tools/lint/test/files/eslint/nolint/foo.txt
new file mode 100644
index 0000000000..e69de29bb2
--- /dev/null
+++ b/tools/lint/test/files/eslint/nolint/foo.txt
diff --git a/tools/lint/test/files/eslint/subdir/bad.js b/tools/lint/test/files/eslint/subdir/bad.js
new file mode 100644
index 0000000000..9d2dd18f39
--- /dev/null
+++ b/tools/lint/test/files/eslint/subdir/bad.js
@@ -0,0 +1,2 @@
+// Missing semicolon
+let foo = "bar"
diff --git a/tools/lint/test/files/file-perm/maybe-shebang/bad.js b/tools/lint/test/files/file-perm/maybe-shebang/bad.js
new file mode 100755
index 0000000000..1a0b4c5fd6
--- /dev/null
+++ b/tools/lint/test/files/file-perm/maybe-shebang/bad.js
@@ -0,0 +1,2 @@
+# Nothing too
+
diff --git a/tools/lint/test/files/file-perm/maybe-shebang/good.js b/tools/lint/test/files/file-perm/maybe-shebang/good.js
new file mode 100755
index 0000000000..8149c0d4f3
--- /dev/null
+++ b/tools/lint/test/files/file-perm/maybe-shebang/good.js
@@ -0,0 +1,5 @@
+#!/usr/bin/env node
+
+
+# Nothing
+
diff --git a/tools/lint/test/files/file-perm/no-shebang/bad-shebang.c b/tools/lint/test/files/file-perm/no-shebang/bad-shebang.c
new file mode 100755
index 0000000000..7151678efa
--- /dev/null
+++ b/tools/lint/test/files/file-perm/no-shebang/bad-shebang.c
@@ -0,0 +1,2 @@
+#!/bin/bash
+int main() { return 0; }
diff --git a/tools/lint/test/files/file-perm/no-shebang/bad.c b/tools/lint/test/files/file-perm/no-shebang/bad.c
new file mode 100755
index 0000000000..76e8197013
--- /dev/null
+++ b/tools/lint/test/files/file-perm/no-shebang/bad.c
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tools/lint/test/files/file-perm/no-shebang/bad.png b/tools/lint/test/files/file-perm/no-shebang/bad.png
new file mode 100755
index 0000000000..db3a5fda7e
--- /dev/null
+++ b/tools/lint/test/files/file-perm/no-shebang/bad.png
Binary files differ
diff --git a/tools/lint/test/files/file-perm/no-shebang/good.c b/tools/lint/test/files/file-perm/no-shebang/good.c
new file mode 100644
index 0000000000..76e8197013
--- /dev/null
+++ b/tools/lint/test/files/file-perm/no-shebang/good.c
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tools/lint/test/files/file-whitespace/bad-newline.c b/tools/lint/test/files/file-whitespace/bad-newline.c
new file mode 100644
index 0000000000..3746a0add3
--- /dev/null
+++ b/tools/lint/test/files/file-whitespace/bad-newline.c
@@ -0,0 +1,3 @@
+int main() { return 0; }
+
+ \ No newline at end of file
diff --git a/tools/lint/test/files/file-whitespace/bad-windows.c b/tools/lint/test/files/file-whitespace/bad-windows.c
new file mode 100644
index 0000000000..70d4c697b9
--- /dev/null
+++ b/tools/lint/test/files/file-whitespace/bad-windows.c
@@ -0,0 +1,3 @@
+int main(){
+ return 42;
+}
diff --git a/tools/lint/test/files/file-whitespace/bad.c b/tools/lint/test/files/file-whitespace/bad.c
new file mode 100644
index 0000000000..4309b1f55d
--- /dev/null
+++ b/tools/lint/test/files/file-whitespace/bad.c
@@ -0,0 +1,3 @@
+int main() {
+return 0;
+}
diff --git a/tools/lint/test/files/file-whitespace/bad.js b/tools/lint/test/files/file-whitespace/bad.js
new file mode 100644
index 0000000000..3441696ef1
--- /dev/null
+++ b/tools/lint/test/files/file-whitespace/bad.js
@@ -0,0 +1,3 @@
+# Nothing too
+
+
diff --git a/tools/lint/test/files/file-whitespace/good.c b/tools/lint/test/files/file-whitespace/good.c
new file mode 100644
index 0000000000..76e8197013
--- /dev/null
+++ b/tools/lint/test/files/file-whitespace/good.c
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tools/lint/test/files/file-whitespace/good.js b/tools/lint/test/files/file-whitespace/good.js
new file mode 100644
index 0000000000..8149c0d4f3
--- /dev/null
+++ b/tools/lint/test/files/file-whitespace/good.js
@@ -0,0 +1,5 @@
+#!/usr/bin/env node
+
+
+# Nothing
+
diff --git a/tools/lint/test/files/flake8/.flake8 b/tools/lint/test/files/flake8/.flake8
new file mode 100644
index 0000000000..1933432319
--- /dev/null
+++ b/tools/lint/test/files/flake8/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length = 100
+exclude =
+ subdir/exclude,
diff --git a/tools/lint/test/files/flake8/bad.py b/tools/lint/test/files/flake8/bad.py
new file mode 100644
index 0000000000..9d9751c7eb
--- /dev/null
+++ b/tools/lint/test/files/flake8/bad.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should most certainly cause a failure.")
diff --git a/tools/lint/test/files/flake8/custom/.flake8 b/tools/lint/test/files/flake8/custom/.flake8
new file mode 100644
index 0000000000..cfe68833f2
--- /dev/null
+++ b/tools/lint/test/files/flake8/custom/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length=110
+ignore=
+ F401
diff --git a/tools/lint/test/files/flake8/custom/good.py b/tools/lint/test/files/flake8/custom/good.py
new file mode 100644
index 0000000000..7f9121a2ba
--- /dev/null
+++ b/tools/lint/test/files/flake8/custom/good.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should also not cause a failure.")
diff --git a/tools/lint/test/files/flake8/ext/bad.configure b/tools/lint/test/files/flake8/ext/bad.configure
new file mode 100644
index 0000000000..8214ebb3c0
--- /dev/null
+++ b/tools/lint/test/files/flake8/ext/bad.configure
@@ -0,0 +1,2 @@
+# unused import
+import os
diff --git a/tools/lint/test/files/flake8/subdir/exclude/bad.py b/tools/lint/test/files/flake8/subdir/exclude/bad.py
new file mode 100644
index 0000000000..9d9751c7eb
--- /dev/null
+++ b/tools/lint/test/files/flake8/subdir/exclude/bad.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should most certainly cause a failure.")
diff --git a/tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py b/tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py
new file mode 100644
index 0000000000..9d9751c7eb
--- /dev/null
+++ b/tools/lint/test/files/flake8/subdir/exclude/exclude_subdir/bad.py
@@ -0,0 +1,5 @@
+# Unused import
+import distutils
+
+print("This is a line that is over 80 characters but under 100. It shouldn't fail.")
+print("This is a line that is over not only 80, but 100 characters. It should most certainly cause a failure.")
diff --git a/tools/lint/test/files/fluent-lint/bad.ftl b/tools/lint/test/files/fluent-lint/bad.ftl
new file mode 100644
index 0000000000..ebc0e1a602
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/bad.ftl
@@ -0,0 +1,44 @@
+Blah-blah = Uppercase letters in identifiers are not permitted.
+blah-blah = This is a legal identifier.
+blah_blah = Underscores in identifiers are not permitted.
+
+bad-apostrophe-1 = The bee's knees
+bad-apostrophe-end-1 = The bees' knees
+bad-apostrophe-2 = The bee‘s knees
+bad-single-quote = 'The bee’s knees'
+ok-apostrophe = The bee’s knees
+ok-single-quote = ‘The bee’s knees’
+bad-double-quote = "The bee’s knees"
+good-double-quote = “The bee’s knees”
+bad-ellipsis = The bee’s knees...
+good-ellipsis = The bee’s knees…
+
+embedded-tag = Read more about <a data-l10n-name="privacy-policy"> our privacy policy </a>.
+bad-embedded-tag = Read more about <a data-l10n-name="privacy-policy"> our 'privacy' policy </a>.
+
+Invalid_Id = This identifier is in the exclusions file and will not cause an error.
+
+good-has-attributes =
+ .accessKey = Attribute identifiers are not checked.
+
+bad-has-attributes =
+ .accessKey = Attribute 'values' are checked.
+
+good-function-call = Last modified: { DATETIME($timeChanged, day: "numeric", month: "long", year: "numeric") }
+
+# $engineName (String) - The engine name that will currently be used for the private window.
+good-variable-identifier = { $engineName } is your default search engine in Private Windows
+
+short-id = I am too short
+
+identifiers-in-selectors-should-be-ignored =
+ .label = { $tabCount ->
+ [1] Send Tab to Device
+ [UPPERCASE] Send Tab to Device
+ *[other] Send { $tabCount } Tabs to Device
+ }
+
+this-message-reference-is-ignored =
+ .label = { menu-quit.label }
+
+ok-message-with-html-and-var = This is a <a href="{ $url }">link</a>
diff --git a/tools/lint/test/files/fluent-lint/brand-names-excluded.ftl b/tools/lint/test/files/fluent-lint/brand-names-excluded.ftl
new file mode 100644
index 0000000000..9f3afa28b8
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/brand-names-excluded.ftl
@@ -0,0 +1,2 @@
+# Comment
+bad-firefox1 = Welcome to Firefox
diff --git a/tools/lint/test/files/fluent-lint/brand-names.ftl b/tools/lint/test/files/fluent-lint/brand-names.ftl
new file mode 100644
index 0000000000..c338d920ca
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/brand-names.ftl
@@ -0,0 +1,30 @@
+bad-firefox1 = Welcome to Firefox
+
+# Comment should be ignored when displaying the offset of the error
+bad-firefox2 = Welcome to Firefox again
+bad-firefox2b = <span>Welcome to Firefox<span> again
+bad-firefox3 = <b>Firefox</b>
+bad-firefox-excluded = <b>Firefox</b>
+
+bad-mozilla1 = Welcome to Mozilla
+bad-mozilla2 = Welcome to Mozilla again
+bad-mozilla2b = <span>Welcome to Mozilla</span> again
+bad-mozilla3 = <b>Mozilla</b>
+
+bad-thunderbird1 = Welcome to Thunderbird
+bad-thunderbird2 = <span>Welcome to Thunderbird</span> again
+bad-thunderbird3 = <b>Thunderbird</b>
+
+good-firefox1 = Welcome to { -brand-firefox }
+good-firefox2 = Welcome to { firefox-message }
+
+good-mozilla1 = Welcome to { -brand-mozilla }
+good-mozilla2 = Welcome to { mozilla-message }
+
+good-thunderbird1 = Welcome to { -brand-thunderbird }
+good-thunderbird2 = Welcome to { thunderbird-message }
+
+# There are no brand checks on terms
+-brand-firefox = Firefox
+
+bland-message = No brands here.
diff --git a/tools/lint/test/files/fluent-lint/comment-group1.ftl b/tools/lint/test/files/fluent-lint/comment-group1.ftl
new file mode 100644
index 0000000000..32c19dc441
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-group1.ftl
@@ -0,0 +1,35 @@
+# 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/.
+
+### Test group comments.
+
+fake-identifier-1 = Fake text
+
+## Pass: This group comment has proper spacing.
+
+fake-identifier-2 = Fake text
+## Fail: (GC03) Group comments should have an empty line before them.
+
+fake-identifier-3 = Fake text
+
+## Fail: (GC02) Group comments should have an empty line after them.
+fake-identifier-4 = Fake text
+
+## Pass: A single comment is fine.
+
+## Fail: (GC04) A group comment must be followed by at least one message.
+
+fake-identifier-5 = Fake text
+
+
+## Fail: (GC03) Only allow 1 line above.
+
+fake-identifier-6 = Fake text
+
+## Fail: (GC02) Only allow 1 line below.
+
+
+fake-identifier-6 = Fake text
+
+## Fail: (GC01) Group comments should not be at the end of a file.
diff --git a/tools/lint/test/files/fluent-lint/comment-group2.ftl b/tools/lint/test/files/fluent-lint/comment-group2.ftl
new file mode 100644
index 0000000000..47d29fa211
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-group2.ftl
@@ -0,0 +1,15 @@
+# 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/.
+
+### Test group comments.
+
+## Pass: This group comment is followed by a term
+
+-fake-term = Fake text
+
+## Pass: The last group comment is allowed to be an empty ##
+
+fake-identifier-1 = Fake text
+
+##
diff --git a/tools/lint/test/files/fluent-lint/comment-resource1.ftl b/tools/lint/test/files/fluent-lint/comment-resource1.ftl
new file mode 100644
index 0000000000..f5d5e53d59
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-resource1.ftl
@@ -0,0 +1,11 @@
+# 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/.
+
+### Pass: This is a resource comment with proper spacing.
+
+fake-identifier-1 = Fake text
+
+### Fail: (RC01) There should not be more than one resource comment
+
+fake-identifier-2 = Fake text
diff --git a/tools/lint/test/files/fluent-lint/comment-resource2.ftl b/tools/lint/test/files/fluent-lint/comment-resource2.ftl
new file mode 100644
index 0000000000..44a77f4e73
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-resource2.ftl
@@ -0,0 +1,6 @@
+# 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/.
+### Fail: (RC03) There should be an empty line preceeding.
+
+fake-identifier-1 = Fake text
diff --git a/tools/lint/test/files/fluent-lint/comment-resource3.ftl b/tools/lint/test/files/fluent-lint/comment-resource3.ftl
new file mode 100644
index 0000000000..b261404380
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-resource3.ftl
@@ -0,0 +1,6 @@
+# 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/.
+
+### Fail: (RC02) There should be an empty line following.
+fake-identifier-1 = Fake text
diff --git a/tools/lint/test/files/fluent-lint/comment-resource4.ftl b/tools/lint/test/files/fluent-lint/comment-resource4.ftl
new file mode 100644
index 0000000000..c24e8887f8
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-resource4.ftl
@@ -0,0 +1,8 @@
+# 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/.
+
+
+### Fail: (RC03) There should be only one space above.
+
+fake-identifier-1 = Fake text
diff --git a/tools/lint/test/files/fluent-lint/comment-resource5.ftl b/tools/lint/test/files/fluent-lint/comment-resource5.ftl
new file mode 100644
index 0000000000..60d8e8c264
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-resource5.ftl
@@ -0,0 +1,8 @@
+# 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/.
+
+### Fail: (RC02) There should be only one space below.
+
+
+fake-identifier-1 = Fake text
diff --git a/tools/lint/test/files/fluent-lint/comment-resource6.ftl b/tools/lint/test/files/fluent-lint/comment-resource6.ftl
new file mode 100644
index 0000000000..a2ca9abfe7
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/comment-resource6.ftl
@@ -0,0 +1,4 @@
+### Pass: Check two conditions.
+### 1. This is an edge case, but we shouldn't error if there is only a resource comment.
+### 2. Make sure this linter does not error if there is no license header. The license is
+### checked with `mach lint license`.
diff --git a/tools/lint/test/files/fluent-lint/excluded.ftl b/tools/lint/test/files/fluent-lint/excluded.ftl
new file mode 100644
index 0000000000..79fe509ad6
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/excluded.ftl
@@ -0,0 +1,6 @@
+# This file is used to test excluding paths from tests.
+Blah-blah = Uppercase letters in identifiers are not permitted.
+blah-blah = This is a legal identifier.
+blah_blah = Underscores in identifiers are not permitted.
+
+bad-apostrophe-1 = The bee's knees
diff --git a/tools/lint/test/files/fluent-lint/tools/lint/fluent-lint/exclusions.yml b/tools/lint/test/files/fluent-lint/tools/lint/fluent-lint/exclusions.yml
new file mode 100644
index 0000000000..1aecf8cedd
--- /dev/null
+++ b/tools/lint/test/files/fluent-lint/tools/lint/fluent-lint/exclusions.yml
@@ -0,0 +1,17 @@
+# 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/.
+---
+ID01:
+ messages:
+ - Invalid_Id
+ files:
+ - excluded.ftl
+ID02:
+ messages: []
+ files: []
+CO01:
+ messages:
+ - bad-firefox-excluded
+ files:
+ - brand-names-excluded.ftl
diff --git a/tools/lint/test/files/isort/.flake8 b/tools/lint/test/files/isort/.flake8
new file mode 100644
index 0000000000..1933432319
--- /dev/null
+++ b/tools/lint/test/files/isort/.flake8
@@ -0,0 +1,4 @@
+[flake8]
+max-line-length = 100
+exclude =
+ subdir/exclude,
diff --git a/tools/lint/test/files/isort/bad.py b/tools/lint/test/files/isort/bad.py
new file mode 100644
index 0000000000..1871917373
--- /dev/null
+++ b/tools/lint/test/files/isort/bad.py
@@ -0,0 +1,8 @@
+import prova
+import collections
+
+
+def foobar():
+ c = collections.Counter()
+ prova.ciao(c)
+
diff --git a/tools/lint/test/files/isort/subdir/exclude/bad.py b/tools/lint/test/files/isort/subdir/exclude/bad.py
new file mode 100644
index 0000000000..58f5363ff0
--- /dev/null
+++ b/tools/lint/test/files/isort/subdir/exclude/bad.py
@@ -0,0 +1,9 @@
+import collections
+
+import prova
+
+
+def foobar():
+ c = collections.Counter()
+ prova.ciao(c)
+
diff --git a/tools/lint/test/files/license/.eslintrc.js b/tools/lint/test/files/license/.eslintrc.js
new file mode 100644
index 0000000000..0449fdfa33
--- /dev/null
+++ b/tools/lint/test/files/license/.eslintrc.js
@@ -0,0 +1,5 @@
+
+// Dot file to verify that it works
+// without license
+
+"use strict";
diff --git a/tools/lint/test/files/license/bad.c b/tools/lint/test/files/license/bad.c
new file mode 100644
index 0000000000..76e8197013
--- /dev/null
+++ b/tools/lint/test/files/license/bad.c
@@ -0,0 +1 @@
+int main() { return 0; }
diff --git a/tools/lint/test/files/license/bad.js b/tools/lint/test/files/license/bad.js
new file mode 100644
index 0000000000..5de1a72f1f
--- /dev/null
+++ b/tools/lint/test/files/license/bad.js
@@ -0,0 +1,6 @@
+/*
+ * This program is free software: you can redistribute it and/or modify
+ * it under the terms of the GNU General Pulic Unknown License as published by
+ * the Free Software Foundation, version 3.
+ *
+ */
diff --git a/tools/lint/test/files/license/good-other.h b/tools/lint/test/files/license/good-other.h
new file mode 100644
index 0000000000..fb915e9b26
--- /dev/null
+++ b/tools/lint/test/files/license/good-other.h
@@ -0,0 +1,9 @@
+/*
+Permission to use, copy, modify, distribute and sell this software
+and its documentation for any purpose is hereby granted without fee,
+provided that the above copyright notice appear in all copies and
+that both that copyright notice and this permission notice appear
+in supporting documentation. Samphan Raruenrom makes no
+representations about the suitability of this software for any
+purpose. It is provided "as is" without express or implied warranty.
+*/
diff --git a/tools/lint/test/files/license/good.c b/tools/lint/test/files/license/good.c
new file mode 100644
index 0000000000..d1a6827fb1
--- /dev/null
+++ b/tools/lint/test/files/license/good.c
@@ -0,0 +1,8 @@
+
+/* -*- Mode: C++; tab-width: 8; indent-tabs-mode: nil; c-basic-offset: 2 -*- */
+/* vim: set ts=8 sts=2 et sw=2 tw=80: */
+/* 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/. */
+
+int main() { return 0; }
diff --git a/tools/lint/test/files/license/good.js b/tools/lint/test/files/license/good.js
new file mode 100644
index 0000000000..d10ae3a8d5
--- /dev/null
+++ b/tools/lint/test/files/license/good.js
@@ -0,0 +1,7 @@
+#!/usr/bin/env node
+/* 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/. */
+
+# Nothing
+
diff --git a/tools/lint/test/files/lintpref/bad.js b/tools/lint/test/files/lintpref/bad.js
new file mode 100644
index 0000000000..ab55ba5dad
--- /dev/null
+++ b/tools/lint/test/files/lintpref/bad.js
@@ -0,0 +1,2 @@
+// Real test pref, matching value.
+pref("dom.webidl.test1", true);
diff --git a/tools/lint/test/files/lintpref/good.js b/tools/lint/test/files/lintpref/good.js
new file mode 100644
index 0000000000..0bf81c8f58
--- /dev/null
+++ b/tools/lint/test/files/lintpref/good.js
@@ -0,0 +1,6 @@
+// Fake prefs.
+pref("foo.bar", 1);
+pref("foo.baz", "1.234");
+
+// Real test pref, different value.
+pref("dom.webidl.test1", false);
diff --git a/tools/lint/test/files/pylint/bad.py b/tools/lint/test/files/pylint/bad.py
new file mode 100644
index 0000000000..61b69a49cf
--- /dev/null
+++ b/tools/lint/test/files/pylint/bad.py
@@ -0,0 +1,5 @@
+def foo():
+ useless_var = 1
+ useless_var = true
+ return "true"
+ print("unreachable") \ No newline at end of file
diff --git a/tools/lint/test/files/pylint/good.py b/tools/lint/test/files/pylint/good.py
new file mode 100644
index 0000000000..c867dc66ec
--- /dev/null
+++ b/tools/lint/test/files/pylint/good.py
@@ -0,0 +1,3 @@
+def foo():
+ a = 1 + 1
+ return a
diff --git a/tools/lint/test/files/rst/.dotfile.rst b/tools/lint/test/files/rst/.dotfile.rst
new file mode 100644
index 0000000000..be24e1d161
--- /dev/null
+++ b/tools/lint/test/files/rst/.dotfile.rst
@@ -0,0 +1,11 @@
+============
+Coding style
+==========
+
+foo bar
+~~~~~
+
+
+This file has error but should not be there
+as we don't analyze dot files
+
diff --git a/tools/lint/test/files/rst/bad.rst b/tools/lint/test/files/rst/bad.rst
new file mode 100644
index 0000000000..c9b60f613e
--- /dev/null
+++ b/tools/lint/test/files/rst/bad.rst
@@ -0,0 +1,20 @@
+============
+Coding style
+============
+
+
+This document attempts to explain the basic styles and patterns used in
+the Mozilla codebase. New code should try to conform to these standards,
+so it is as easy to maintain as existing code. There are exceptions, but
+it's still important to know the rules!
+
+
+Whitespace
+~~~~~~~~
+
+Line length
+~~~~~~~~~~~
+
+Line length
+~~~~~~~~~~~
+
diff --git a/tools/lint/test/files/rst/bad2.rst b/tools/lint/test/files/rst/bad2.rst
new file mode 100644
index 0000000000..81c35dde06
--- /dev/null
+++ b/tools/lint/test/files/rst/bad2.rst
@@ -0,0 +1,4 @@
+====
+Test
+===
+
diff --git a/tools/lint/test/files/rst/bad3.rst b/tools/lint/test/files/rst/bad3.rst
new file mode 100644
index 0000000000..b7e66e5c92
--- /dev/null
+++ b/tools/lint/test/files/rst/bad3.rst
@@ -0,0 +1,6 @@
+
+.. _When_Should_I_Use_a_Hashtable.3F:
+
+When Should I Use a Hashtable?
+------------------------------
+
diff --git a/tools/lint/test/files/rst/good.rst b/tools/lint/test/files/rst/good.rst
new file mode 100644
index 0000000000..fd12da85d3
--- /dev/null
+++ b/tools/lint/test/files/rst/good.rst
@@ -0,0 +1,11 @@
+============
+Coding style
+============
+
+
+This document attempts to explain the basic styles and patterns used in
+the Mozilla codebase. New code should try to conform to these standards,
+so it is as easy to maintain as existing code. There are exceptions, but
+it's still important to know the rules!
+
+
diff --git a/tools/lint/test/files/rustfmt/subdir/bad.rs b/tools/lint/test/files/rustfmt/subdir/bad.rs
new file mode 100644
index 0000000000..fb1746fafd
--- /dev/null
+++ b/tools/lint/test/files/rustfmt/subdir/bad.rs
@@ -0,0 +1,16 @@
+fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+ // Clippy detects this as a swap and considers this as an error
+ let mut a =
+ 1;
+ let mut b=1;
+
+ a =
+ b;
+ b = a;
+
+
+}
diff --git a/tools/lint/test/files/rustfmt/subdir/bad2.rs b/tools/lint/test/files/rustfmt/subdir/bad2.rs
new file mode 100644
index 0000000000..a4236a2de7
--- /dev/null
+++ b/tools/lint/test/files/rustfmt/subdir/bad2.rs
@@ -0,0 +1,17 @@
+fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+ let mut a;
+ let mut b=1;
+ let mut vec = Vec::new();
+ vec.push(1);
+ vec.push(2);
+
+
+ for x in 5..10 - 5 {
+ a = x;
+ }
+
+ }
diff --git a/tools/lint/test/files/rustfmt/subdir/good.rs b/tools/lint/test/files/rustfmt/subdir/good.rs
new file mode 100644
index 0000000000..9bcaee67b7
--- /dev/null
+++ b/tools/lint/test/files/rustfmt/subdir/good.rs
@@ -0,0 +1,6 @@
+fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+}
diff --git a/tools/lint/test/files/shellcheck/bad.sh b/tools/lint/test/files/shellcheck/bad.sh
new file mode 100644
index 0000000000..b2eb195558
--- /dev/null
+++ b/tools/lint/test/files/shellcheck/bad.sh
@@ -0,0 +1,3 @@
+#!/bin/sh
+hello="Hello world"
+echo $1
diff --git a/tools/lint/test/files/shellcheck/good.sh b/tools/lint/test/files/shellcheck/good.sh
new file mode 100644
index 0000000000..e61d501955
--- /dev/null
+++ b/tools/lint/test/files/shellcheck/good.sh
@@ -0,0 +1,2 @@
+#!/bin/sh
+echo "Hello world"
diff --git a/tools/lint/test/files/trojan-source/README b/tools/lint/test/files/trojan-source/README
new file mode 100644
index 0000000000..343a9d0c3c
--- /dev/null
+++ b/tools/lint/test/files/trojan-source/README
@@ -0,0 +1,5 @@
+These examples are taken from trojan source:
+https://github.com/nickboucher/trojan-source
+
+The examples are published under the MIT license.
+
diff --git a/tools/lint/test/files/trojan-source/commenting-out.cpp b/tools/lint/test/files/trojan-source/commenting-out.cpp
new file mode 100644
index 0000000000..d67df70ce1
--- /dev/null
+++ b/tools/lint/test/files/trojan-source/commenting-out.cpp
@@ -0,0 +1,9 @@
+#include <iostream>
+
+int main() {
+ bool isAdmin = false;
+ /*‮ } ⁦if (isAdmin)⁩ ⁦ begin admins only */
+ std::cout << "You are an admin.\n";
+ /* end admins only ‮ { ⁦*/
+ return 0;
+} \ No newline at end of file
diff --git a/tools/lint/test/files/trojan-source/early-return.py b/tools/lint/test/files/trojan-source/early-return.py
new file mode 100644
index 0000000000..2797d8ae9f
--- /dev/null
+++ b/tools/lint/test/files/trojan-source/early-return.py
@@ -0,0 +1,9 @@
+#!/usr/bin/env python3
+bank = { 'alice': 100 }
+
+def subtract_funds(account: str, amount: int):
+ ''' Subtract funds from bank account then ⁧''' ;return
+ bank[account] -= amount
+ return
+
+subtract_funds('alice', 50) \ No newline at end of file
diff --git a/tools/lint/test/files/trojan-source/invisible-function.rs b/tools/lint/test/files/trojan-source/invisible-function.rs
new file mode 100644
index 0000000000..b32efb0372
--- /dev/null
+++ b/tools/lint/test/files/trojan-source/invisible-function.rs
@@ -0,0 +1,15 @@
+fn isAdmin() {
+ return false;
+}
+
+fn is​Admin() {
+ return true;
+}
+
+fn main() {
+ if is​Admin() {
+ printf("You are an admin\n");
+ } else {
+ printf("You are NOT an admin.\n");
+ }
+} \ No newline at end of file
diff --git a/tools/lint/test/files/updatebot/.yamllint b/tools/lint/test/files/updatebot/.yamllint
new file mode 100644
index 0000000000..4f11bbd6c5
--- /dev/null
+++ b/tools/lint/test/files/updatebot/.yamllint
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# Explicity default .yamllint to isolate tests from tree-wide yamlint config.
+---
+extends: default
diff --git a/tools/lint/test/files/updatebot/cargo-mismatch.yaml b/tools/lint/test/files/updatebot/cargo-mismatch.yaml
new file mode 100644
index 0000000000..ac18d2b87c
--- /dev/null
+++ b/tools/lint/test/files/updatebot/cargo-mismatch.yaml
@@ -0,0 +1,44 @@
+---
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Core
+ component: "Graphics: WebGPU"
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: wgpu
+
+ description: A cross-platform pure-Rust graphics API
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://github.com/gfx-rs/wgpu
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: commit 32af4f56
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred)
+ revision: idontmatchanything
+
+ license: ['MIT', 'Apache-2.0']
+
+updatebot:
+ maintainer-phab: jimb
+ maintainer-bz: jimb@mozilla.com
+ tasks:
+ - type: vendoring
+ enabled: true
+ frequency: 1 week
+
+vendoring:
+ url: https://github.com/gfx-rs/wgpu
+ source-hosting: github
+ vendor-directory: gfx/wgpu_bindings/
+ flavor: rust
diff --git a/tools/lint/test/files/updatebot/good1.yaml b/tools/lint/test/files/updatebot/good1.yaml
new file mode 100644
index 0000000000..f57d2c5b4c
--- /dev/null
+++ b/tools/lint/test/files/updatebot/good1.yaml
@@ -0,0 +1,44 @@
+---
+schema: 1
+
+bugzilla:
+ product: Core
+ component: Graphics
+
+origin:
+ name: angle
+
+ description: ANGLE - Almost Native Graphics Layer Engine
+
+ url: https://chromium.googlesource.com/angle/angle
+
+ # Note that while the vendoring information here, including revision,
+ # release, and upstream repo locations refer to the third party upstream,
+ # Angle is vendored from a mozilla git repository that pulls from
+ # upstream and mainntains local patches there.
+ release: commit 018f85dea11fd5e41725750c6958695a6b8e8409
+ revision: 018f85dea11fd5e41725750c6958695a6b8e8409
+
+ license: BSD-3-Clause
+
+updatebot:
+ maintainer-phab: jgilbert
+ maintainer-bz: jgilbert@mozilla.com
+ tasks:
+ - type: commit-alert
+ enabled: true
+ branch: chromium/4515
+ needinfo: ["jgilbert@mozilla.com"]
+
+vendoring:
+ url: https://chromium.googlesource.com/angle/angle
+ tracking: tag
+ source-hosting: angle
+ vendor-directory: gfx/angle/checkout
+ skip-vendoring-steps: ["fetch", "update-moz-build"]
+
+ update-actions:
+ - action: run-script
+ script: '{yaml_dir}/auto-update-angle.sh'
+ args: ['{revision}']
+ cwd: '{cwd}'
diff --git a/tools/lint/test/files/updatebot/good2.yaml b/tools/lint/test/files/updatebot/good2.yaml
new file mode 100644
index 0000000000..0161d28b11
--- /dev/null
+++ b/tools/lint/test/files/updatebot/good2.yaml
@@ -0,0 +1,74 @@
+---
+# Version of this schema
+schema: 1
+
+bugzilla:
+ # Bugzilla product and component for this directory and subdirectories
+ product: Core
+ component: "Audio/Video: Playback"
+
+# Document the source of externally hosted code
+origin:
+
+ # Short name of the package/library
+ name: dav1d
+
+ description: dav1d, a fast AV1 decoder
+
+ # Full URL for the package's homepage/etc
+ # Usually different from repository url
+ url: https://code.videolan.org/videolan/dav1d
+
+ # Human-readable identifier for this version/release
+ # Generally "version NNN", "tag SSS", "bookmark SSS"
+ release: ffb59680356fd210816cf9e46d9d023ade1f4d5a
+
+ # Revision to pull in
+ # Must be a long or short commit SHA (long preferred)
+ revision: ffb59680356fd210816cf9e46d9d023ade1f4d5a
+
+ # The package's license, where possible using the mnemonic from
+ # https://spdx.org/licenses/
+ # Multiple licenses can be specified (as a YAML list)
+ # A "LICENSE" file must exist containing the full license text
+ license: BSD-2-Clause
+
+ license-file: COPYING
+
+updatebot:
+ maintainer-phab: chunmin
+ maintainer-bz: cchang@mozilla.com
+ tasks:
+ - type: vendoring
+ enabled: true
+ frequency: release
+
+vendoring:
+ url: https://code.videolan.org/videolan/dav1d
+ source-hosting: gitlab
+ vendor-directory: third_party/dav1d
+
+ exclude:
+ - build/.gitattributes
+ - build/.gitignore
+ - doc
+ - examples
+ - package
+ - tools
+
+ generated:
+ - '{yaml_dir}/vcs_version.h'
+ - '{yaml_dir}/version.h'
+
+ update-actions:
+ - action: copy-file
+ from: include/vcs_version.h.in
+ to: '{yaml_dir}/vcs_version.h'
+ - action: replace-in-file
+ pattern: '@VCS_TAG@'
+ with: '{revision}'
+ file: '{yaml_dir}/vcs_version.h'
+ - action: run-script
+ script: '{yaml_dir}/update-version.sh'
+ cwd: '{vendor_dir}'
+ args: ['{yaml_dir}/version.h']
diff --git a/tools/lint/test/files/updatebot/no-revision.yaml b/tools/lint/test/files/updatebot/no-revision.yaml
new file mode 100644
index 0000000000..4d581508d8
--- /dev/null
+++ b/tools/lint/test/files/updatebot/no-revision.yaml
@@ -0,0 +1,43 @@
+---
+schema: 1
+
+bugzilla:
+ product: Core
+ component: Graphics
+
+origin:
+ name: angle
+
+ description: ANGLE - Almost Native Graphics Layer Engine
+
+ url: https://chromium.googlesource.com/angle/angle
+
+ # Note that while the vendoring information here, including revision,
+ # release, and upstream repo locations refer to the third party upstream,
+ # Angle is vendored from a mozilla git repository that pulls from
+ # upstream and mainntains local patches there.
+ release: commit 018f85dea11fd5e41725750c6958695a6b8e8409
+
+ license: BSD-3-Clause
+
+updatebot:
+ maintainer-phab: jgilbert
+ maintainer-bz: jgilbert@mozilla.com
+ tasks:
+ - type: commit-alert
+ enabled: true
+ branch: chromium/4515
+ needinfo: ["jgilbert@mozilla.com"]
+
+vendoring:
+ url: https://chromium.googlesource.com/angle/angle
+ tracking: tag
+ source-hosting: angle
+ vendor-directory: gfx/angle/checkout
+ skip-vendoring-steps: ["fetch", "update-moz-build"]
+
+ update-actions:
+ - action: run-script
+ script: '{yaml_dir}/auto-update-angle.sh'
+ args: ['{revision}']
+ cwd: '{cwd}'
diff --git a/tools/lint/test/files/yaml/.yamllint b/tools/lint/test/files/yaml/.yamllint
new file mode 100644
index 0000000000..4f11bbd6c5
--- /dev/null
+++ b/tools/lint/test/files/yaml/.yamllint
@@ -0,0 +1,6 @@
+# Any copyright is dedicated to the Public Domain.
+# http://creativecommons.org/publicdomain/zero/1.0/
+
+# Explicity default .yamllint to isolate tests from tree-wide yamlint config.
+---
+extends: default
diff --git a/tools/lint/test/files/yaml/bad.yml b/tools/lint/test/files/yaml/bad.yml
new file mode 100644
index 0000000000..195ac7b030
--- /dev/null
+++ b/tools/lint/test/files/yaml/bad.yml
@@ -0,0 +1,8 @@
+---
+yamllint:
+ description: YAML linteraaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaaax
+ include:
+ - .cron.yml
+ - browser/config/
+ - wrong
+ application:bar
diff --git a/tools/lint/test/files/yaml/good.yml b/tools/lint/test/files/yaml/good.yml
new file mode 100644
index 0000000000..b30941b797
--- /dev/null
+++ b/tools/lint/test/files/yaml/good.yml
@@ -0,0 +1,6 @@
+---
+yamllint:
+ description: YAML linter
+ include:
+ - .cron.yml
+ - browser/config/
diff --git a/tools/lint/test/python.ini b/tools/lint/test/python.ini
new file mode 100644
index 0000000000..5c03ae9395
--- /dev/null
+++ b/tools/lint/test/python.ini
@@ -0,0 +1,33 @@
+[DEFAULT]
+subsuite = mozlint
+
+[test_android_format.py]
+[test_black.py]
+requirements = tools/lint/python/black_requirements.txt
+[test_clang_format.py]
+[test_clippy.py]
+[test_codespell.py]
+[test_eslint.py]
+skip-if = os == "win" # busts the tree for subsequent tasks on the same worker (bug 1708591)
+[test_file_license.py]
+[test_file_perm.py]
+skip-if = os == "win"
+[test_file_whitespace.py]
+[test_flake8.py]
+requirements = tools/lint/python/flake8_requirements.txt
+[test_fluent_lint.py]
+[test_lintpref.py]
+[test_perfdocs.py]
+[test_perfdocs_generation.py]
+[test_updatebot.py]
+[test_perfdocs_helpers.py]
+[test_pylint.py]
+requirements = tools/lint/python/pylint_requirements.txt
+[test_rst.py]
+requirements = tools/lint/rst/requirements.txt
+[test_rustfmt.py]
+[test_shellcheck.py]
+[test_trojan_source.py]
+[test_yaml.py]
+[test_isort.py]
+requirements = tools/lint/python/isort_requirements.txt
diff --git a/tools/lint/test/test_android_format.py b/tools/lint/test/test_android_format.py
new file mode 100644
index 0000000000..70cd1ea02e
--- /dev/null
+++ b/tools/lint/test/test_android_format.py
@@ -0,0 +1,38 @@
+import mozunit
+from conftest import build
+
+LINTER = "android-format"
+
+
+def test_basic(global_lint, config):
+ substs = {
+ "GRADLE_ANDROID_FORMAT_LINT_CHECK_TASKS": [
+ "spotlessJavaCheck",
+ "spotlessKotlinCheck",
+ ],
+ "GRADLE_ANDROID_FORMAT_LINT_FIX_TASKS": [
+ "spotlessJavaApply",
+ "spotlessKotlinApply",
+ ],
+ "GRADLE_ANDROID_FORMAT_LINT_FOLDERS": ["tools/lint/test/files/android-format"],
+ }
+ results = global_lint(
+ config=config,
+ topobjdir=build.topobjdir,
+ root=build.topsrcdir,
+ substs=substs,
+ extra_args=["-PandroidFormatLintTest"],
+ )
+ print(results)
+
+ # When first task (spotlessJavaCheck) hits error, we won't check next Kotlin error.
+ # So results length will be 1.
+ assert len(results) == 1
+ assert results[0].level == "error"
+
+ # Since android-format is global lint, fix=True overrides repository files directly.
+ # No way to add this test.
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_black.py b/tools/lint/test/test_black.py
new file mode 100644
index 0000000000..9027670665
--- /dev/null
+++ b/tools/lint/test/test_black.py
@@ -0,0 +1,53 @@
+# -*- coding: utf-8 -*-
+
+# 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 mozunit
+
+LINTER = "black"
+fixed = 0
+
+
+def test_lint_fix(lint, create_temp_file):
+
+ contents = """def is_unique(
+ s
+ ):
+ s = list(s
+ )
+ s.sort()
+
+
+ for i in range(len(s) - 1):
+ if s[i] == s[i + 1]:
+ return 0
+ else:
+ return 1
+
+
+if __name__ == "__main__":
+ print(
+ is_unique(input())
+ ) """
+
+ path = create_temp_file(contents, "bad.py")
+ lint([path], fix=True)
+ assert fixed == 1
+
+
+def test_lint_black(lint, paths):
+ results = lint(paths())
+ assert len(results) == 2
+
+ assert results[0].level == "error"
+ assert results[0].relpath == "bad.py"
+
+ assert "EOF" in results[1].message
+ assert results[1].level == "error"
+ assert results[1].relpath == "invalid.py"
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_clang_format.py b/tools/lint/test/test_clang_format.py
new file mode 100644
index 0000000000..809280601d
--- /dev/null
+++ b/tools/lint/test/test_clang_format.py
@@ -0,0 +1,139 @@
+import mozunit
+from conftest import build
+
+LINTER = "clang-format"
+fixed = 0
+
+
+def test_good(lint, config, paths):
+ results = lint(paths("good/"), root=build.topsrcdir, use_filters=False)
+ print(results)
+ assert len(results) == 0
+
+ results = lint(paths("good/"), root=build.topsrcdir, use_filters=False, fix=True)
+ assert fixed == len(results)
+
+
+def test_basic(lint, config, paths):
+ results = lint(paths("bad/bad.cpp"), root=build.topsrcdir, use_filters=False)
+ print(results)
+ assert len(results) == 1
+
+ assert "Reformat C/C++" in results[0].message
+ assert results[0].level == "warning"
+ assert results[0].lineno == 1
+ assert results[0].column == 0
+ assert "bad.cpp" in results[0].path
+ assert (
+ results[0].diff
+ == """\
+-int main ( ) {
+-
+-return 0;
+-
+-
+-}
++int main() { return 0; }
+""" # noqa
+ )
+
+
+def test_dir(lint, config, paths):
+ results = lint(paths("bad/"), root=build.topsrcdir, use_filters=False)
+ print(results)
+ assert len(results) == 5
+
+ assert "Reformat C/C++" in results[0].message
+ assert results[0].level == "warning"
+ assert results[0].lineno == 1
+ assert results[0].column == 0
+ assert "bad.cpp" in results[0].path
+ assert (
+ results[0].diff
+ == """\
+-int main ( ) {
+-
+-return 0;
+-
+-
+-}
++int main() { return 0; }
+""" # noqa
+ )
+
+ assert "Reformat C/C++" in results[1].message
+ assert results[1].level == "warning"
+ assert results[1].lineno == 1
+ assert results[1].column == 0
+ assert "bad2.c" in results[1].path
+ assert (
+ results[1].diff
+ == """\
+-#include "bad2.h"
+-
+-
+-int bad2() {
++#include "bad2.h"
++
++int bad2() {
+"""
+ )
+
+ assert "Reformat C/C++" in results[2].message
+ assert results[2].level == "warning"
+ assert results[2].lineno == 5
+ assert results[2].column == 0
+ assert "bad2.c" in results[2].path
+ assert (
+ results[2].diff
+ == """\
+- int a =2;
++ int a = 2;
+"""
+ )
+
+ assert "Reformat C/C++" in results[3].message
+ assert results[3].level == "warning"
+ assert results[3].lineno == 6
+ assert results[3].column == 0
+ assert "bad2.c" in results[3].path
+ assert (
+ results[3].diff
+ == """\
+- return a;
+-
+-}
++ return a;
++}
+"""
+ )
+
+ assert "Reformat C/C++" in results[4].message
+ assert results[4].level == "warning"
+ assert results[4].lineno == 1
+ assert results[4].column == 0
+ assert "bad2.h" in results[4].path
+ assert (
+ results[4].diff
+ == """\
+-int bad2(void );
++int bad2(void);
+"""
+ )
+
+
+def test_fixed(lint, create_temp_file):
+
+ contents = """int main ( ) { \n
+return 0; \n
+
+}"""
+
+ path = create_temp_file(contents, "ignore.cpp")
+ lint([path], use_filters=False, fix=True)
+
+ assert fixed == 1
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_clippy.py b/tools/lint/test/test_clippy.py
new file mode 100644
index 0000000000..6a2687d2c3
--- /dev/null
+++ b/tools/lint/test/test_clippy.py
@@ -0,0 +1,121 @@
+import os
+
+import mozunit
+
+LINTER = "clippy"
+
+
+def test_basic(lint, config, paths):
+ results = lint(paths("test1/"))
+ print(results)
+ assert len(results) > 7
+
+ assert (
+ "is never read" in results[0].message or "but never used" in results[0].message
+ )
+ assert results[0].level == "warning"
+ assert results[0].lineno == 7
+ assert results[0].column >= 9
+ assert results[0].rule == "unused_assignments"
+ assert results[0].relpath == "test1/bad.rs"
+ assert "tools/lint/test/files/clippy/test1/bad.rs" in results[0].path
+
+ assert "this looks like you are trying to swap `a` and `b`" in results[1].message
+ assert results[1].level == "error"
+ assert results[1].relpath == "test1/bad.rs"
+ assert results[1].rule == "clippy::almost_swapped"
+ assert "or maybe you should use `std::mem::replace`" in results[1].hint
+
+ assert "value assigned to `b` is never read" in results[2].message
+ assert results[2].level == "warning"
+ assert results[2].relpath == "test1/bad.rs"
+
+ if "variable does not need to be mutable" in results[5].message:
+ n = 5
+ else:
+ n = 6
+
+ assert "variable does not need to be mutable" in results[n].message
+ assert results[n].relpath == "test1/bad2.rs"
+ assert results[n].rule == "unused_mut"
+
+ assert "unused variable: `vec`" in results[n + 1].message
+ assert results[n + 1].level == "warning"
+ assert results[n + 1].relpath == "test1/bad2.rs"
+ assert results[n + 1].rule == "unused_variables"
+
+ assert "this range is empty so" in results[8].message
+ assert results[8].level == "error"
+ assert results[8].relpath == "test1/bad2.rs"
+ assert results[8].rule == "clippy::reversed_empty_ranges"
+
+
+def test_error(lint, config, paths):
+ results = lint(paths("test1/Cargo.toml"))
+ # Should fail. We don't accept Cargo.toml as input
+ assert results == 1
+
+
+def test_file_and_path_provided(lint, config, paths):
+ results = lint(paths("./test2/src/bad_1.rs", "test1/"))
+ # even if clippy analyzed it
+ # we should not have anything from bad_2.rs
+ # as mozlint is filtering out the file
+ print(results)
+ assert len(results) > 12
+ assert "value assigned to `a` is never read" in results[0].message
+ assert results[0].level == "warning"
+ assert results[0].lineno == 7
+ assert results[0].column >= 9
+ assert results[0].rule == "unused_assignments"
+ assert results[0].relpath == "test1/bad.rs"
+ assert "tools/lint/test/files/clippy/test1/bad.rs" in results[0].path
+ assert "value assigned to `a` is never read" in results[0].message
+ assert results[8].level == "error"
+ assert results[8].lineno == 10
+ assert results[8].column == 14
+ assert results[8].rule == "clippy::reversed_empty_ranges"
+ assert results[8].relpath == "test1/bad2.rs"
+ assert "tools/lint/test/files/clippy/test1/bad2.rs" in results[8].path
+
+ assert results[10].level == "warning"
+ assert results[10].lineno == 9
+ assert results[10].column >= 9
+ assert results[10].rule == "unused_assignments"
+ assert results[10].relpath == "test2/src/bad_1.rs"
+ assert "tools/lint/test/files/clippy/test2/src/bad_1.rs" in results[10].path
+ for r in results:
+ assert "bad_2.rs" not in r.relpath
+
+
+def test_file_provided(lint, config, paths):
+ results = lint(paths("./test2/src/bad_1.rs"))
+ # even if clippy analyzed it
+ # we should not have anything from bad_2.rs
+ # as mozlint is filtering out the file
+ print(results)
+ assert len(results) > 2
+ assert results[0].level == "warning"
+ assert results[0].lineno == 9
+ assert results[0].column >= 9
+ assert results[0].rule == "unused_assignments"
+ assert results[0].relpath == "test2/src/bad_1.rs"
+ assert "tools/lint/test/files/clippy/test2/src/bad_1.rs" in results[0].path
+ for r in results:
+ assert "bad_2.rs" not in r.relpath
+
+
+def test_cleanup(lint, paths, root):
+ # If Cargo.lock does not exist before clippy run, delete it
+ lint(paths("test1/"))
+ assert not os.path.exists(os.path.join(root, "test1/target/"))
+ assert not os.path.exists(os.path.join(root, "test1/Cargo.lock"))
+
+ # If Cargo.lock exists before clippy run, keep it after cleanup
+ lint(paths("test2/"))
+ assert not os.path.exists(os.path.join(root, "test2/target/"))
+ assert os.path.exists(os.path.join(root, "test2/Cargo.lock"))
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_codespell.py b/tools/lint/test/test_codespell.py
new file mode 100644
index 0000000000..8baae66b41
--- /dev/null
+++ b/tools/lint/test/test_codespell.py
@@ -0,0 +1,37 @@
+import mozunit
+
+LINTER = "codespell"
+fixed = 0
+
+
+def test_lint_codespell_fix(lint, create_temp_file):
+ contents = """This is a file with some typos and informations.
+But also testing false positive like optin (because this isn't always option)
+or stuff related to our coding style like:
+aparent (aParent).
+but detects mistakes like mozila
+""".lstrip()
+
+ path = create_temp_file(contents, "ignore.rst")
+ lint([path], fix=True)
+
+ assert fixed == 2
+
+
+def test_lint_codespell(lint, paths):
+ results = lint(paths())
+ assert len(results) == 2
+
+ assert results[0].message == "informations ==> information"
+ assert results[0].level == "error"
+ assert results[0].lineno == 1
+ assert results[0].relpath == "ignore.rst"
+
+ assert results[1].message == "mozila ==> mozilla"
+ assert results[1].level == "error"
+ assert results[1].lineno == 5
+ assert results[1].relpath == "ignore.rst"
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_eslint.py b/tools/lint/test/test_eslint.py
new file mode 100644
index 0000000000..ea96a57717
--- /dev/null
+++ b/tools/lint/test/test_eslint.py
@@ -0,0 +1,74 @@
+import mozunit
+import pytest
+from conftest import build
+
+LINTER = "eslint"
+fixed = 0
+
+
+@pytest.fixture
+def eslint(lint):
+ def inner(*args, **kwargs):
+ kwargs["extra_args"] = ["--no-ignore"]
+ return lint(*args, **kwargs)
+
+ return inner
+
+
+def test_lint_with_global_exclude(lint, config, paths):
+ config["exclude"] = ["subdir", "import"]
+ # This uses lint directly as we need to not ignore the excludes.
+ results = lint(paths(), config=config, root=build.topsrcdir)
+ assert len(results) == 0
+
+
+def test_no_files_to_lint(eslint, config, paths):
+ # A directory with no files to lint.
+ results = eslint(paths("nolint"), root=build.topsrcdir)
+ assert results == []
+
+ # Errors still show up even when a directory with no files is passed in.
+ results = eslint(paths("nolint", "subdir/bad.js"), root=build.topsrcdir)
+ assert len(results) == 1
+
+
+def test_bad_import(eslint, config, paths):
+ results = eslint(paths("import"), config=config, root=build.topsrcdir)
+ assert results == 1
+
+
+def test_rule(eslint, config, create_temp_file):
+ contents = """var re = /foo bar/;
+ var re = new RegExp("foo bar");
+
+"""
+ path = create_temp_file(contents, "bad.js")
+ results = eslint(
+ [path], config=config, root=build.topsrcdir, rules=["no-regex-spaces: error"]
+ )
+
+ assert len(results) == 2
+
+
+def test_fix(eslint, config, create_temp_file):
+ contents = """/*eslint no-regex-spaces: "error"*/
+
+ var re = /foo bar/;
+ var re = new RegExp("foo bar");
+
+
+ var re = /foo bar/;
+ var re = new RegExp("foo bar");
+
+ var re = /foo bar/;
+ var re = new RegExp("foo bar");
+
+"""
+ path = create_temp_file(contents, "bad.js")
+ eslint([path], config=config, root=build.topsrcdir, fix=True)
+
+ assert fixed == 6
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_file_license.py b/tools/lint/test/test_file_license.py
new file mode 100644
index 0000000000..b00b023bae
--- /dev/null
+++ b/tools/lint/test/test_file_license.py
@@ -0,0 +1,23 @@
+import mozunit
+
+LINTER = "license"
+
+
+def test_lint_license(lint, paths):
+ results = lint(paths())
+ print(results)
+ assert len(results) == 3
+
+ assert ".eslintrc.js" in results[0].relpath
+
+ assert "No matching license strings" in results[1].message
+ assert results[1].level == "error"
+ assert "bad.c" in results[1].relpath
+
+ assert "No matching license strings" in results[2].message
+ assert results[2].level == "error"
+ assert "bad.js" in results[2].relpath
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_file_perm.py b/tools/lint/test/test_file_perm.py
new file mode 100644
index 0000000000..08d6a20eef
--- /dev/null
+++ b/tools/lint/test/test_file_perm.py
@@ -0,0 +1,35 @@
+import mozunit
+import pytest
+
+LINTER = "file-perm"
+
+
+@pytest.mark.lint_config(name="file-perm")
+def test_lint_file_perm(lint, paths):
+ results = lint(paths("no-shebang"), collapse_results=True)
+
+ assert results.keys() == {
+ "no-shebang/bad.c",
+ "no-shebang/bad-shebang.c",
+ "no-shebang/bad.png",
+ }
+
+ for path, issues in results.items():
+ for issue in issues:
+ assert "permissions on a source" in issue.message
+ assert issue.level == "error"
+
+
+@pytest.mark.lint_config(name="maybe-shebang-file-perm")
+def test_lint_shebang_file_perm(config, lint, paths):
+ results = lint(paths("maybe-shebang"))
+
+ assert len(results) == 1
+
+ assert "permissions on a source" in results[0].message
+ assert results[0].level == "error"
+ assert results[0].relpath == "maybe-shebang/bad.js"
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_file_whitespace.py b/tools/lint/test/test_file_whitespace.py
new file mode 100644
index 0000000000..51b6fc4795
--- /dev/null
+++ b/tools/lint/test/test_file_whitespace.py
@@ -0,0 +1,51 @@
+import mozunit
+
+LINTER = "file-whitespace"
+fixed = 0
+
+
+def test_lint_file_whitespace(lint, paths):
+ results = lint(paths())
+ print(results)
+ assert len(results) == 5
+
+ assert "File does not end with newline character" in results[1].message
+ assert results[1].level == "error"
+ assert "bad-newline.c" in results[1].relpath
+
+ assert "Empty Lines at end of file" in results[0].message
+ assert results[0].level == "error"
+ assert "bad-newline.c" in results[0].relpath
+
+ assert "Windows line return" in results[2].message
+ assert results[2].level == "error"
+ assert "bad-windows.c" in results[2].relpath
+
+ assert "Trailing whitespace" in results[3].message
+ assert results[3].level == "error"
+ assert "bad.c" in results[3].relpath
+ assert results[3].lineno == 1
+
+ assert "Trailing whitespace" in results[4].message
+ assert results[4].level == "error"
+ assert "bad.c" in results[4].relpath
+ assert results[4].lineno == 2
+
+
+def test_lint_file_whitespace_fix(lint, paths, create_temp_file):
+
+ contents = """int main() { \n
+ return 0; \n
+}
+
+
+"""
+
+ path = create_temp_file(contents, "bad.cpp")
+ lint([path], fix=True)
+ # Gives a different answer on Windows. Probably because of Windows CR
+ assert fixed == 3 or fixed == 2
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_flake8.py b/tools/lint/test/test_flake8.py
new file mode 100644
index 0000000000..d44e3828ed
--- /dev/null
+++ b/tools/lint/test/test_flake8.py
@@ -0,0 +1,117 @@
+import os
+
+import mozunit
+
+LINTER = "flake8"
+fixed = 0
+
+
+def test_lint_single_file(lint, paths):
+ results = lint(paths("bad.py"))
+ assert len(results) == 2
+ assert results[0].rule == "F401"
+ assert results[0].level == "error"
+ assert results[1].rule == "E501"
+ assert results[1].level == "error"
+ assert results[1].lineno == 5
+
+ # run lint again to make sure the previous results aren't counted twice
+ results = lint(paths("bad.py"))
+ assert len(results) == 2
+
+
+def test_lint_custom_config_ignored(lint, paths):
+ results = lint(paths("custom"))
+ assert len(results) == 2
+
+ results = lint(paths("custom/good.py"))
+ assert len(results) == 2
+
+
+def test_lint_fix(lint, create_temp_file):
+ global fixed
+ contents = """
+import distutils
+
+def foobar():
+ pass
+""".lstrip()
+
+ path = create_temp_file(contents, name="bad.py")
+ results = lint([path])
+ assert len(results) == 2
+
+ # Make sure the missing blank line is fixed, but the unused import isn't.
+ results = lint([path], fix=True)
+ assert len(results) == 1
+ assert fixed == 1
+
+ fixed = 0
+
+ # Also test with a directory
+ path = os.path.dirname(create_temp_file(contents, name="bad2.py"))
+ results = lint([path], fix=True)
+ # There should now be two files with 2 combined errors
+ assert len(results) == 2
+ assert fixed == 1
+ assert all(r.rule != "E501" for r in results)
+
+
+def test_lint_fix_uses_config(lint, create_temp_file):
+ contents = """
+foo = ['A list of strings', 'that go over 80 characters', 'to test if autopep8 fixes it']
+""".lstrip()
+
+ path = create_temp_file(contents, name="line_length.py")
+ lint([path], fix=True)
+
+ # Make sure autopep8 reads the global config under lintargs['root']. If it
+ # didn't, then the line-length over 80 would get fixed.
+ with open(path, "r") as fh:
+ assert fh.read() == contents
+
+
+def test_lint_excluded_file(lint, paths, config):
+ # First file is globally excluded, second one is from .flake8 config.
+ files = paths("bad.py", "subdir/exclude/bad.py", "subdir/exclude/exclude_subdir")
+ config["exclude"] = paths("bad.py")
+ results = lint(files, config)
+ print(results)
+ assert len(results) == 0
+
+ # Make sure excludes also apply when running from a different cwd.
+ cwd = paths("subdir")[0]
+ os.chdir(cwd)
+
+ results = lint(paths("subdir/exclude"))
+ print(results)
+ assert len(results) == 0
+
+
+def test_lint_excluded_file_with_glob(lint, paths, config):
+ config["exclude"] = paths("ext/*.configure")
+
+ files = paths("ext")
+ results = lint(files, config)
+ print(results)
+ assert len(results) == 0
+
+ files = paths("ext/bad.configure")
+ results = lint(files, config)
+ print(results)
+ assert len(results) == 0
+
+
+def test_lint_excluded_file_with_no_filter(lint, paths, config):
+ results = lint(paths("subdir/exclude"), use_filters=False)
+ print(results)
+ assert len(results) == 4
+
+
+def test_lint_uses_custom_extensions(lint, paths):
+ assert len(lint(paths("ext"))) == 1
+ assert len(lint(paths("ext/bad.configure"))) == 1
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_fluent_lint.py b/tools/lint/test/test_fluent_lint.py
new file mode 100644
index 0000000000..3d3a7b9f0e
--- /dev/null
+++ b/tools/lint/test/test_fluent_lint.py
@@ -0,0 +1,134 @@
+# 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 mozunit
+
+LINTER = "fluent-lint"
+
+
+def test_lint_exclusions(lint, paths):
+ results = lint(paths("excluded.ftl"))
+ assert len(results) == 1
+ assert results[0].rule == "TE01"
+ assert results[0].lineno == 6
+ assert results[0].column == 20
+
+
+def test_lint_single_file(lint, paths):
+ results = lint(paths("bad.ftl"))
+ assert len(results) == 11
+ assert results[0].rule == "ID01"
+ assert results[0].lineno == 1
+ assert results[0].column == 1
+ assert results[1].rule == "ID01"
+ assert results[1].lineno == 3
+ assert results[1].column == 1
+ assert results[2].rule == "TE01"
+ assert results[2].lineno == 5
+ assert results[2].column == 20
+ assert results[3].rule == "TE01"
+ assert results[3].lineno == 6
+ assert results[3].column == 24
+ assert results[4].rule == "TE02"
+ assert results[4].lineno == 7
+ assert results[4].column == 20
+ assert results[5].rule == "TE03"
+ assert results[5].lineno == 8
+ assert results[5].column == 20
+ assert results[6].rule == "TE04"
+ assert results[6].lineno == 11
+ assert results[6].column == 20
+ assert results[7].rule == "TE05"
+ assert results[7].lineno == 13
+ assert results[7].column == 16
+ assert results[8].rule == "TE03"
+ assert results[8].lineno == 17
+ assert results[8].column == 20
+ assert results[9].rule == "TE03"
+ assert results[9].lineno == 25
+ assert results[9].column == 18
+ assert results[10].rule == "ID02"
+ assert results[10].lineno == 32
+ assert results[10].column == 1
+
+
+def test_comment_group(lint, paths):
+ results = lint(paths("comment-group1.ftl"))
+ assert len(results) == 6
+ assert results[0].rule == "GC03"
+ assert results[0].lineno == 12
+ assert results[0].column == 1
+ assert results[1].rule == "GC02"
+ assert results[1].lineno == 16
+ assert results[1].column == 1
+ assert results[2].rule == "GC04"
+ assert results[2].lineno == 21
+ assert results[2].column == 1
+ assert results[3].rule == "GC03"
+ assert results[3].lineno == 26
+ assert results[3].column == 1
+ assert results[4].rule == "GC02"
+ assert results[4].lineno == 30
+ assert results[4].column == 1
+ assert results[5].rule == "GC01"
+ assert results[5].lineno == 35
+ assert results[5].column == 1
+
+ results = lint(paths("comment-group2.ftl"))
+ assert (len(results)) == 0
+
+
+def test_comment_resource(lint, paths):
+ results = lint(paths("comment-resource1.ftl"))
+ assert len(results) == 1
+ assert results[0].rule == "RC01"
+ assert results[0].lineno == 9
+ assert results[0].column == 1
+
+ results = lint(paths("comment-resource2.ftl"))
+ assert len(results) == 1
+ assert results[0].rule == "RC03"
+ assert results[0].lineno == 4
+ assert results[0].column == 1
+
+ results = lint(paths("comment-resource3.ftl"))
+ assert len(results) == 1
+ assert results[0].rule == "RC02"
+ assert results[0].lineno == 5
+ assert results[0].column == 1
+
+ results = lint(paths("comment-resource4.ftl"))
+ assert len(results) == 1
+ assert results[0].rule == "RC03"
+ assert results[0].lineno == 6
+ assert results[0].column == 1
+
+ results = lint(paths("comment-resource5.ftl"))
+ assert len(results) == 1
+ assert results[0].rule == "RC02"
+ assert results[0].lineno == 5
+ assert results[0].column == 1
+
+ results = lint(paths("comment-resource6.ftl"))
+ assert len(results) == 0
+
+
+def test_brand_names(lint, paths):
+ results = lint(paths("brand-names.ftl"))
+ assert len(results) == 11
+ assert results[0].rule == "CO01"
+ assert results[0].lineno == 1
+ assert results[0].column == 16
+ assert "Firefox" in results[0].message
+ assert "Mozilla" not in results[0].message
+ assert "Thunderbird" not in results[0].message
+ assert results[1].rule == "CO01"
+ assert results[1].lineno == 4
+ assert results[1].column == 16
+
+ results = lint(paths("brand-names-excluded.ftl"))
+ assert len(results) == 0
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_isort.py b/tools/lint/test/test_isort.py
new file mode 100644
index 0000000000..180bfbebd0
--- /dev/null
+++ b/tools/lint/test/test_isort.py
@@ -0,0 +1,114 @@
+# -*- coding: utf-8 -*-
+
+# 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 mozunit
+
+LINTER = "isort"
+fixed = 0
+
+
+def test_lint_fix(lint, create_temp_file):
+ contents = """
+import prova
+import collections
+
+
+def foobar():
+ c = collections.Counter()
+ prova.ciao(c)
+""".lstrip()
+
+ path = create_temp_file(contents, name="bad.py")
+ results = lint([path])
+ assert len(results) == 1
+ assert results[0].level == "error"
+
+ lint([path], fix=True)
+ assert fixed == 1
+
+
+def test_lint_excluded_file(lint, paths, config):
+ # Second file is excluded from .flake8 config.
+ files = paths("bad.py", "subdir/exclude/bad.py", "subdir/exclude/exclude_subdir")
+ results = lint(files, config)
+ assert len(results) == 1
+
+ # First file is globally excluded, second one is from .flake8 config.
+ files = paths("bad.py", "subdir/exclude/bad.py", "subdir/exclude/exclude_subdir")
+ config["exclude"] = paths("bad.py")
+ results = lint(files, config)
+ assert len(results) == 0
+
+ # Make sure excludes also apply when running from a different cwd.
+ cwd = paths("subdir")[0]
+ os.chdir(cwd)
+
+ results = lint(paths("subdir/exclude"))
+ assert len(results) == 0
+
+
+def test_lint_uses_all_configs(lint, paths, tmpdir):
+ myself = tmpdir.join("myself")
+ myself.mkdir()
+
+ flake8_path = tmpdir.join(".flake8")
+ flake8_path.write(
+ """
+[flake8]
+exclude =
+""".lstrip()
+ )
+
+ py_path = myself.join("good.py")
+ py_path.write(
+ """
+import os
+
+from myself import something_else
+from third_party import something
+
+
+def ciao():
+ pass
+""".lstrip()
+ )
+
+ results = lint([py_path.strpath])
+ assert len(results) == 0
+
+ isort_cfg_path = myself.join(".isort.cfg")
+ isort_cfg_path.write(
+ """
+[settings]
+known_first_party = myself
+""".lstrip()
+ )
+
+ results = lint([py_path.strpath], root=tmpdir.strpath)
+ assert len(results) == 1
+
+ py_path.write(
+ """
+import os
+
+from third_party import something
+
+from myself import something_else
+
+
+def ciao():
+ pass
+""".lstrip()
+ )
+
+ results = lint([py_path.strpath], root=tmpdir.strpath)
+ assert len(results) == 0
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_lintpref.py b/tools/lint/test/test_lintpref.py
new file mode 100644
index 0000000000..3e75b1675e
--- /dev/null
+++ b/tools/lint/test/test_lintpref.py
@@ -0,0 +1,16 @@
+import mozunit
+
+LINTER = "lintpref"
+
+
+def test_lintpref(lint, paths):
+ results = lint(paths())
+ assert len(results) == 1
+ assert results[0].level == "error"
+ assert 'pref("dom.webidl.test1", true);' in results[0].message
+ assert "bad.js" in results[0].relpath
+ assert results[0].lineno == 2
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_perfdocs.py b/tools/lint/test/test_perfdocs.py
new file mode 100644
index 0000000000..260d6d9917
--- /dev/null
+++ b/tools/lint/test/test_perfdocs.py
@@ -0,0 +1,846 @@
+import contextlib
+import os
+import pathlib
+import shutil
+import tempfile
+from unittest import mock
+
+import mozunit
+import pytest
+
+LINTER = "perfdocs"
+
+
+class PerfDocsLoggerMock:
+ LOGGER = None
+ PATHS = []
+ FAILED = True
+
+
+"""
+This is a sample mozperftest test that we use for testing
+the verification process.
+"""
+SAMPLE_TEST = """
+"use strict";
+
+async function setUp(context) {
+ context.log.info("setUp example!");
+}
+
+async function test(context, commands) {
+ context.log.info("Test with setUp/tearDown example!");
+ await commands.measure.start("https://www.sitespeed.io/");
+ await commands.measure.start("https://www.mozilla.org/en-US/");
+}
+
+async function tearDown(context) {
+ context.log.info("tearDown example!");
+}
+
+module.noexport = {};
+
+module.exports = {
+ setUp,
+ tearDown,
+ test,
+ owner: "Performance Testing Team",
+ name: "Example",
+ description: "The description of the example test.",
+ longDescription: `
+ This is a longer description of the test perhaps including information
+ about how it should be run locally or links to relevant information.
+ `
+};
+"""
+
+
+SAMPLE_CONFIG = """
+name: mozperftest
+manifest: None
+static-only: False
+suites:
+ suite:
+ description: "Performance tests from the 'suite' folder."
+ tests:
+ Example: ""
+"""
+
+
+DYNAMIC_SAMPLE_CONFIG = """
+name: {}
+manifest: None
+static-only: False
+suites:
+ suite:
+ description: "Performance tests from the 'suite' folder."
+ tests:
+ Example: "Performance test Example from suite."
+ another_suite:
+ description: "Performance tests from the 'another_suite' folder."
+ tests:
+ Example: "Performance test Example from another_suite."
+"""
+
+
+SAMPLE_METRICS_CONFIG = """
+name: raptor
+manifest: "None"{}
+static-only: False
+suites:
+ suite:
+ description: "Performance tests from the 'suite' folder."{}
+ tests:
+ Example: "Performance test Example from another_suite."
+ another_suite:
+ description: "Performance tests from the 'another_suite' folder."
+ tests:
+ Example: "Performance test Example from another_suite."
+"""
+
+
+SAMPLE_INI = """
+[Example]
+test_url = Example_url
+alert_on = fcp
+"""
+
+SAMPLE_METRICS_INI = """
+[Example]
+test_url = Example_url
+alert_on = fcp,SpeedIndex
+"""
+
+
+@contextlib.contextmanager
+def temp_file(name="temp", tempdir=None, content=None):
+ if tempdir is None:
+ tempdir = tempfile.mkdtemp()
+ path = pathlib.Path(tempdir, name)
+ if content is not None:
+ with path.open("w", newline="\n") as f:
+ f.write(content)
+ try:
+ yield path
+ finally:
+ try:
+ shutil.rmtree(str(tempdir))
+ except FileNotFoundError:
+ pass
+
+
+@contextlib.contextmanager
+def temp_dir():
+ tempdir = pathlib.Path(tempfile.mkdtemp())
+ try:
+ yield tempdir
+ finally:
+ try:
+ shutil.rmtree(str(tempdir))
+ except FileNotFoundError:
+ pass
+
+
+def setup_sample_logger(logger, structured_logger, top_dir):
+ from perfdocs.logger import PerfDocLogger
+
+ PerfDocLogger.LOGGER = structured_logger
+ PerfDocLogger.PATHS = ["perfdocs"]
+ PerfDocLogger.TOP_DIR = top_dir
+
+ import perfdocs.gatherer as gt
+ import perfdocs.generator as gn
+ import perfdocs.verifier as vf
+
+ gt.logger = logger
+ vf.logger = logger
+ gn.logger = logger
+
+
+@mock.patch("perfdocs.generator.Generator")
+@mock.patch("perfdocs.verifier.Verifier")
+@mock.patch("perfdocs.logger.PerfDocLogger", new=PerfDocsLoggerMock)
+def test_perfdocs_start_and_fail(verifier, generator, structured_logger, config, paths):
+ from perfdocs.perfdocs import run_perfdocs
+
+ with temp_file("bad", content="foo") as temp:
+ run_perfdocs(
+ config, logger=structured_logger, paths=[str(temp)], generate=False
+ )
+ assert PerfDocsLoggerMock.LOGGER == structured_logger
+ assert PerfDocsLoggerMock.PATHS == [temp]
+ assert PerfDocsLoggerMock.FAILED
+
+ assert verifier.call_count == 1
+ assert mock.call().validate_tree() in verifier.mock_calls
+ assert generator.call_count == 0
+
+
+@mock.patch("perfdocs.generator.Generator")
+@mock.patch("perfdocs.verifier.Verifier")
+@mock.patch("perfdocs.logger.PerfDocLogger", new=PerfDocsLoggerMock)
+def test_perfdocs_start_and_pass(verifier, generator, structured_logger, config, paths):
+ from perfdocs.perfdocs import run_perfdocs
+
+ PerfDocsLoggerMock.FAILED = False
+ with temp_file("bad", content="foo") as temp:
+ run_perfdocs(
+ config, logger=structured_logger, paths=[str(temp)], generate=False
+ )
+ assert PerfDocsLoggerMock.LOGGER == structured_logger
+ assert PerfDocsLoggerMock.PATHS == [temp]
+ assert not PerfDocsLoggerMock.FAILED
+
+ assert verifier.call_count == 1
+ assert mock.call().validate_tree() in verifier.mock_calls
+ assert generator.call_count == 1
+ assert mock.call().generate_perfdocs() in generator.mock_calls
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger", new=PerfDocsLoggerMock)
+def test_perfdocs_bad_paths(structured_logger, config, paths):
+ from perfdocs.perfdocs import run_perfdocs
+
+ with pytest.raises(Exception):
+ run_perfdocs(config, logger=structured_logger, paths=["bad"], generate=False)
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_gatherer_fetch_perfdocs_tree(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.gatherer import Gatherer
+
+ gatherer = Gatherer(top_dir)
+ assert not gatherer._perfdocs_tree
+
+ gatherer.fetch_perfdocs_tree()
+
+ expected = "Found 1 perfdocs directories"
+ args, _ = logger.log.call_args
+
+ assert expected in args[0]
+ assert logger.log.call_count == 1
+ assert gatherer._perfdocs_tree
+
+ expected = ["path", "yml", "rst", "static"]
+ for i, key in enumerate(gatherer._perfdocs_tree[0].keys()):
+ assert key == expected[i]
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_gatherer_get_test_list(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.gatherer import Gatherer
+
+ gatherer = Gatherer(top_dir)
+ gatherer.fetch_perfdocs_tree()
+ framework = gatherer.get_test_list(gatherer._perfdocs_tree[0])
+
+ expected = ["name", "test_list", "yml_content", "yml_path"]
+ for i, key in enumerate(sorted(framework.keys())):
+ assert key == expected[i]
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verification(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ # Make sure that we had no warnings
+ assert logger.warning.call_count == 0
+ assert logger.log.call_count == 1
+ assert len(logger.mock_calls) == 1
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_validate_yaml_pass(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ yaml_path = perfdocs_sample["config"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ valid = Verifier(top_dir).validate_yaml(pathlib.Path(yaml_path))
+
+ assert valid
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_invalid_yaml(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ yaml_path = perfdocs_sample["config"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier("top_dir")
+ with open(yaml_path, "r", newline="\n") as f:
+ lines = f.readlines()
+ print(lines)
+ with open(yaml_path, "w", newline="\n") as f:
+ f.write("\n".join(lines[2:]))
+ valid = verifier.validate_yaml(yaml_path)
+
+ expected = ("YAML ValidationError: 'name' is a required property\n", yaml_path)
+ args, _ = logger.warning.call_args
+
+ assert logger.warning.call_count == 1
+ assert expected[0] in args[0]
+ assert not valid
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_validate_rst_pass(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ rst_path = perfdocs_sample["index"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ valid = Verifier(top_dir).validate_rst_content(pathlib.Path(rst_path))
+
+ assert valid
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_invalid_rst(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ rst_path = perfdocs_sample["index"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ # Replace the target string to invalid Keyword for test
+ with open(rst_path, "r") as file:
+ filedata = file.read()
+
+ filedata = filedata.replace("documentation", "Invalid Keyword")
+
+ with open(rst_path, "w", newline="\n") as file:
+ file.write(filedata)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier("top_dir")
+ valid = verifier.validate_rst_content(rst_path)
+
+ expected = (
+ "Cannot find a '{documentation}' entry in the given index file",
+ rst_path,
+ )
+ args, _ = logger.warning.call_args
+
+ assert logger.warning.call_count == 1
+ assert args == expected
+ assert not valid
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_validate_descriptions_pass(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0])
+
+ assert logger.warning.call_count == 0
+ assert logger.log.call_count == 1
+ assert len(logger.mock_calls) == 1
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_not_existing_suite_in_test_list(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ manifest_path = perfdocs_sample["manifest"]["path"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ os.remove(manifest_path)
+ verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0])
+
+ expected = (
+ "Could not find an existing suite for suite - bad suite name?",
+ perfdocs_sample["config"],
+ )
+ args, _ = logger.warning.call_args
+
+ assert logger.warning.call_count == 1
+ assert args == expected
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_not_existing_tests_in_suites(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ with open(perfdocs_sample["config"], "r") as file:
+ filedata = file.read()
+ filedata = filedata.replace("Example", "DifferentName")
+ with open(perfdocs_sample["config"], "w", newline="\n") as file:
+ file.write(filedata)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0])
+
+ expected = [
+ "Could not find an existing test for DifferentName - bad test name?",
+ "Could not find a test description for Example",
+ ]
+
+ assert logger.warning.call_count == 2
+ for i, call in enumerate(logger.warning.call_args_list):
+ args, _ = call
+ assert args[0] == expected[i]
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_missing_contents_in_suite(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ with open(perfdocs_sample["config"], "r") as file:
+ filedata = file.read()
+ filedata = filedata.replace("suite:", "InvalidSuite:")
+ with open(perfdocs_sample["config"], "w", newline="\n") as file:
+ file.write(filedata)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier._check_framework_descriptions(verifier._gatherer.perfdocs_tree[0])
+
+ expected = (
+ "Could not find an existing suite for InvalidSuite - bad suite name?",
+ "Missing suite description for suite",
+ )
+
+ assert logger.warning.call_count == 2
+ for i, call in enumerate(logger.warning.call_args_list):
+ args, _ = call
+ assert args[0] == expected[i]
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_invalid_dir(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier("invalid_path")
+ with pytest.raises(Exception) as exceinfo:
+ verifier.validate_tree()
+
+ assert str(exceinfo.value) == "No valid perfdocs directories found"
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_file_invalidation(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.verifier import Verifier
+
+ with mock.patch("perfdocs.verifier.Verifier.validate_yaml", return_value=False):
+ verifier = Verifier(top_dir)
+ with pytest.raises(Exception):
+ verifier.validate_tree()
+
+ # Check if "File validation error" log is called
+ # and Called with a log inside perfdocs_tree().
+ assert logger.log.call_count == 2
+ assert len(logger.mock_calls) == 2
+
+
+@pytest.mark.parametrize(
+ "manifest, metric_definitions, expected",
+ [
+ [
+ SAMPLE_INI,
+ """
+metrics:
+ "FirstPaint":
+ aliases:
+ - fcp
+ description: "Example" """,
+ 1,
+ ],
+ [
+ SAMPLE_METRICS_INI,
+ """
+metrics:
+ FirstPaint:
+ aliases:
+ - fcp
+ description: Example
+ SpeedIndex:
+ aliases:
+ - speedindex
+ - si
+ description: Example
+ """,
+ 2,
+ ],
+ ],
+)
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_nonexistent_documented_metrics(
+ logger, structured_logger, perfdocs_sample, manifest, metric_definitions, expected
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ with open(perfdocs_sample["config"], "w", newline="\n") as f:
+ f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, ""))
+ with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f:
+ f.write(manifest)
+
+ sample_gatherer_result = {
+ "suite": {"Example": {}},
+ "another_suite": {"Example": {}},
+ }
+
+ from perfdocs.verifier import Verifier
+
+ with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m:
+ m.return_value = sample_gatherer_result
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ assert len(logger.warning.call_args_list) == expected
+ for (args, _) in logger.warning.call_args_list:
+ assert "Cannot find documented metric" in args[0]
+ assert "being used" in args[0]
+
+
+@pytest.mark.parametrize(
+ "manifest, metric_definitions",
+ [
+ [
+ SAMPLE_INI,
+ """
+metrics:
+ "FirstPaint":
+ aliases:
+ - fcp
+ description: "Example" """,
+ ],
+ [
+ SAMPLE_METRICS_INI,
+ """
+metrics:
+ SpeedIndex:
+ aliases:
+ - speedindex
+ - si
+ description: Example
+ """,
+ ],
+ ],
+)
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_undocumented_metrics(
+ logger, structured_logger, perfdocs_sample, manifest, metric_definitions
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ with open(perfdocs_sample["config"], "w", newline="\n") as f:
+ f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, ""))
+ with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f:
+ f.write(manifest)
+
+ sample_gatherer_result = {
+ "suite": {"Example": {"metrics": ["fcp", "SpeedIndex"]}},
+ "another_suite": {"Example": {}},
+ }
+
+ from perfdocs.verifier import Verifier
+
+ with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m:
+ m.return_value = sample_gatherer_result
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ assert len(logger.warning.call_args_list) == 1
+ for (args, _) in logger.warning.call_args_list:
+ assert "Missing description for the metric" in args[0]
+
+
+@pytest.mark.parametrize(
+ "manifest, metric_definitions, expected",
+ [
+ [
+ SAMPLE_INI,
+ """
+metrics:
+ "FirstPaint":
+ aliases:
+ - fcp
+ - SpeedIndex
+ description: "Example" """,
+ 3,
+ ],
+ [
+ SAMPLE_METRICS_INI,
+ """
+metrics:
+ FirstPaint:
+ aliases:
+ - fcp
+ description: Example
+ SpeedIndex:
+ aliases:
+ - speedindex
+ - si
+ description: Example
+ """,
+ 5,
+ ],
+ ],
+)
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_duplicate_metrics(
+ logger, structured_logger, perfdocs_sample, manifest, metric_definitions, expected
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ with open(perfdocs_sample["config"], "w", newline="\n") as f:
+ indented_defs = "\n".join(
+ [(" " * 8) + metric_line for metric_line in metric_definitions.split("\n")]
+ )
+ f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, indented_defs))
+ with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f:
+ f.write(manifest)
+
+ sample_gatherer_result = {
+ "suite": {"Example": {"metrics": ["fcp", "SpeedIndex"]}},
+ "another_suite": {"Example": {}},
+ }
+
+ from perfdocs.verifier import Verifier
+
+ with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m:
+ m.return_value = sample_gatherer_result
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ assert len(logger.warning.call_args_list) == expected
+ for (args, _) in logger.warning.call_args_list:
+ assert "Duplicate definitions found for " in args[0]
+
+
+@pytest.mark.parametrize(
+ "manifest, metric_definitions",
+ [
+ [
+ SAMPLE_INI,
+ """
+metrics:
+ "FirstPaint":
+ aliases:
+ - fcp
+ - SpeedIndex
+ description: "Example" """,
+ ],
+ [
+ SAMPLE_METRICS_INI,
+ """
+metrics:
+ FirstPaint:
+ aliases:
+ - fcp
+ description: Example
+ SpeedIndex:
+ aliases:
+ - speedindex
+ - si
+ description: Example
+ """,
+ ],
+ ],
+)
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_verifier_valid_metrics(
+ logger, structured_logger, perfdocs_sample, manifest, metric_definitions
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ with open(perfdocs_sample["config"], "w", newline="\n") as f:
+ f.write(SAMPLE_METRICS_CONFIG.format(metric_definitions, ""))
+ with open(perfdocs_sample["manifest"]["path"], "w", newline="\n") as f:
+ f.write(manifest)
+
+ sample_gatherer_result = {
+ "suite": {"Example": {"metrics": ["fcp", "SpeedIndex"]}},
+ "another_suite": {"Example": {}},
+ }
+
+ from perfdocs.verifier import Verifier
+
+ with mock.patch("perfdocs.framework_gatherers.RaptorGatherer.get_test_list") as m:
+ m.return_value = sample_gatherer_result
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ assert len(logger.warning.call_args_list) == 0
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_framework_gatherers(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ # Check to make sure that every single framework
+ # gatherer that has been implemented produces a test list
+ # in every suite that contains a test with an associated
+ # manifest.
+ from perfdocs.gatherer import frameworks
+
+ for framework, gatherer in frameworks.items():
+ with open(perfdocs_sample["config"], "w", newline="\n") as f:
+ f.write(DYNAMIC_SAMPLE_CONFIG.format(framework))
+
+ fg = gatherer(perfdocs_sample["config"], top_dir)
+ if getattr(fg, "get_test_list", None) is None:
+ # Skip framework gatherers that have not
+ # implemented a method to build a test list.
+ continue
+
+ # Setup some framework-specific things here if needed
+ if framework == "raptor":
+ fg._manifest_path = perfdocs_sample["manifest"]["path"]
+ fg._get_subtests_from_ini = mock.Mock()
+ fg._get_subtests_from_ini.return_value = {
+ "Example": perfdocs_sample["manifest"],
+ }
+
+ if framework == "talos":
+ fg._get_ci_tasks = mock.Mock()
+ for suite, suitetests in fg.get_test_list().items():
+ assert suite == "Talos Tests"
+ assert suitetests
+ continue
+
+ if framework == "awsy":
+ for suite, suitetests in fg.get_test_list().items():
+ assert suite == "Awsy tests"
+ assert suitetests
+ continue
+
+ for suite, suitetests in fg.get_test_list().items():
+ assert suite == "suite"
+ for test, manifest in suitetests.items():
+ assert test == "Example"
+ assert (
+ pathlib.Path(manifest["path"])
+ == perfdocs_sample["manifest"]["path"]
+ )
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_framework_gatherers_urls(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.gatherer import frameworks
+ from perfdocs.generator import Generator
+ from perfdocs.utils import read_yaml
+ from perfdocs.verifier import Verifier
+
+ # This test is only for raptor
+ gatherer = frameworks["raptor"]
+ with open(perfdocs_sample["config"], "w", newline="\n") as f:
+ f.write(DYNAMIC_SAMPLE_CONFIG.format("raptor"))
+
+ fg = gatherer(perfdocs_sample["config_2"], top_dir)
+ fg.get_suite_list = mock.Mock()
+ fg.get_suite_list.return_value = {
+ "suite": [perfdocs_sample["example1_manifest"]],
+ "another_suite": [perfdocs_sample["example2_manifest"]],
+ }
+
+ v = Verifier(top_dir)
+ gn = Generator(v, generate=True, workspace=top_dir)
+
+ # Check to make sure that if a test is present under multiple
+ # suties the urls are generated correctly for the test under
+ # every suite
+ for suite, suitetests in fg.get_test_list().items():
+ url = fg._descriptions.get(suite)
+ assert url is not None
+ assert url[0]["name"] == "Example"
+ assert url[0]["test_url"] == "Example_url"
+
+ perfdocs_tree = gn._perfdocs_tree[0]
+ yaml_content = read_yaml(
+ pathlib.Path(
+ os.path.join(os.path.join(perfdocs_tree["path"], perfdocs_tree["yml"]))
+ )
+ )
+ suites = yaml_content["suites"]
+
+ # Check that the sections for each suite are generated correctly
+ for suite_name, suite_details in suites.items():
+ gn._verifier._gatherer = mock.Mock(framework_gatherers={"raptor": gatherer})
+ section = gn._verifier._gatherer.framework_gatherers[
+ "raptor"
+ ].build_suite_section(fg, suite_name, suites.get(suite_name)["description"])
+ assert suite_name.capitalize() == section[0]
+ assert suite_name in section[2]
+
+ tests = suites.get(suite_name).get("tests", {})
+ for test_name in tests.keys():
+ desc = gn._verifier._gatherer.framework_gatherers[
+ "raptor"
+ ].build_test_description(fg, test_name, tests[test_name], suite_name)
+ assert f"**test url**: `<{url[0]['test_url']}>`__" in desc[0]
+ assert f"**expected**: {url[0]['expected']}" in desc[0]
+ assert test_name in desc[0]
+
+
+def test_perfdocs_logger_failure(config, paths):
+ from perfdocs.logger import PerfDocLogger
+
+ PerfDocLogger.LOGGER = None
+ with pytest.raises(Exception):
+ PerfDocLogger()
+
+ PerfDocLogger.PATHS = []
+ with pytest.raises(Exception):
+ PerfDocLogger()
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_perfdocs_generation.py b/tools/lint/test/test_perfdocs_generation.py
new file mode 100644
index 0000000000..29b1555467
--- /dev/null
+++ b/tools/lint/test/test_perfdocs_generation.py
@@ -0,0 +1,297 @@
+import os
+import pathlib
+from unittest import mock
+
+import mozunit
+
+LINTER = "perfdocs"
+
+
+def setup_sample_logger(logger, structured_logger, top_dir):
+ from perfdocs.logger import PerfDocLogger
+
+ PerfDocLogger.LOGGER = structured_logger
+ PerfDocLogger.PATHS = ["perfdocs"]
+ PerfDocLogger.TOP_DIR = top_dir
+
+ import perfdocs.gatherer as gt
+ import perfdocs.generator as gn
+ import perfdocs.utils as utils
+ import perfdocs.verifier as vf
+
+ gt.logger = logger
+ vf.logger = logger
+ gn.logger = logger
+ utils.logger = logger
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_generate_perfdocs_pass(
+ logger, structured_logger, perfdocs_sample
+):
+ from test_perfdocs import temp_file
+
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates")
+ templates_dir.mkdir(parents=True, exist_ok=True)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+ with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"):
+ generator.generate_perfdocs()
+
+ assert logger.warning.call_count == 0
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_needed_regeneration(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=False, workspace=top_dir)
+ generator.generate_perfdocs()
+
+ expected = "PerfDocs need to be regenerated."
+ args, _ = logger.warning.call_args
+
+ assert logger.warning.call_count == 1
+ assert args[0] == expected
+
+
+@mock.patch("perfdocs.generator.get_changed_files", new=lambda x: [])
+@mock.patch("perfdocs.generator.ON_TRY", new=True)
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_needed_update(logger, structured_logger, perfdocs_sample):
+ from test_perfdocs import temp_file
+
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates")
+ templates_dir.mkdir(parents=True, exist_ok=True)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ # Initializing perfdocs
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+ with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"):
+ generator.generate_perfdocs()
+
+ # Removed file for testing and run again
+ generator._generate = False
+ files = [f for f in os.listdir(generator.perfdocs_path)]
+ for f in files:
+ os.remove(str(pathlib.Path(generator.perfdocs_path, f)))
+
+ generator.generate_perfdocs()
+
+ expected = (
+ "PerfDocs are outdated, run ./mach lint -l perfdocs --fix .` to update them. "
+ "You can also apply the perfdocs.diff patch file produced from this "
+ "reviewbot test to fix the issue."
+ )
+ args, _ = logger.warning.call_args
+
+ assert logger.warning.call_count == 1
+ assert args[0] == expected
+
+ # Check to ensure a diff was produced
+ assert logger.log.call_count == 6
+
+ logs = [v[0][0] for v in logger.log.call_args_list]
+ for failure_log in (
+ "Some files are missing or are funny.",
+ "Missing in existing docs: index.rst",
+ "Missing in existing docs: mozperftest.rst",
+ ):
+ assert failure_log in logs
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_created_perfdocs(
+ logger, structured_logger, perfdocs_sample
+):
+ from test_perfdocs import temp_file
+
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates")
+ templates_dir.mkdir(parents=True, exist_ok=True)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+ with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"):
+ perfdocs_tmpdir = generator._create_perfdocs()
+
+ files = [f for f in os.listdir(perfdocs_tmpdir)]
+ files.sort()
+ expected_files = ["index.rst", "mozperftest.rst"]
+
+ for i, file in enumerate(files):
+ assert file == expected_files[i]
+
+ with pathlib.Path(perfdocs_tmpdir, expected_files[0]).open() as f:
+ filedata = f.readlines()
+ assert "".join(filedata) == " * :doc:`mozperftest`"
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_build_perfdocs(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+ frameworks_info = generator.build_perfdocs_from_tree()
+
+ expected = ["dynamic", "static"]
+
+ for framework in sorted(frameworks_info.keys()):
+ for i, framework_info in enumerate(frameworks_info[framework]):
+ assert framework_info == expected[i]
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_create_temp_dir(logger, structured_logger, perfdocs_sample):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+ tmpdir = generator._create_temp_dir()
+
+ assert pathlib.Path(tmpdir).is_dir()
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_create_temp_dir_fail(
+ logger, structured_logger, perfdocs_sample
+):
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+ with mock.patch("perfdocs.generator.pathlib") as path_mock:
+ path_mock.Path().mkdir.side_effect = OSError()
+ path_mock.Path().is_dir.return_value = False
+ tmpdir = generator._create_temp_dir()
+
+ expected = "Error creating temp file: "
+ args, _ = logger.critical.call_args
+
+ assert not tmpdir
+ assert logger.critical.call_count == 1
+ assert args[0] == expected
+
+
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_save_perfdocs_pass(
+ logger, structured_logger, perfdocs_sample
+):
+ from test_perfdocs import temp_file
+
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates")
+ templates_dir.mkdir(parents=True, exist_ok=True)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+
+ assert not generator.perfdocs_path.is_dir()
+
+ with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"):
+ perfdocs_tmpdir = generator._create_perfdocs()
+
+ generator._save_perfdocs(perfdocs_tmpdir)
+
+ expected = ["index.rst", "mozperftest.rst"]
+ files = [f for f in os.listdir(generator.perfdocs_path)]
+ files.sort()
+
+ for i, file in enumerate(files):
+ assert file == expected[i]
+
+
+@mock.patch("perfdocs.generator.shutil")
+@mock.patch("perfdocs.logger.PerfDocLogger")
+def test_perfdocs_generator_save_perfdocs_fail(
+ logger, shutil, structured_logger, perfdocs_sample
+):
+ from test_perfdocs import temp_file
+
+ top_dir = perfdocs_sample["top_dir"]
+ setup_sample_logger(logger, structured_logger, top_dir)
+
+ templates_dir = pathlib.Path(top_dir, "tools", "lint", "perfdocs", "templates")
+ templates_dir.mkdir(parents=True, exist_ok=True)
+
+ from perfdocs.generator import Generator
+ from perfdocs.verifier import Verifier
+
+ verifier = Verifier(top_dir)
+ verifier.validate_tree()
+
+ generator = Generator(verifier, generate=True, workspace=top_dir)
+ with temp_file("index.rst", tempdir=templates_dir, content="{test_documentation}"):
+ perfdocs_tmpdir = generator._create_perfdocs()
+
+ shutil.copytree = mock.Mock(side_effect=Exception())
+ generator._save_perfdocs(perfdocs_tmpdir)
+
+ expected = "There was an error while saving the documentation: "
+ args, _ = logger.critical.call_args
+
+ assert logger.critical.call_count == 1
+ assert args[0] == expected
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_perfdocs_helpers.py b/tools/lint/test/test_perfdocs_helpers.py
new file mode 100644
index 0000000000..02c1abfecc
--- /dev/null
+++ b/tools/lint/test/test_perfdocs_helpers.py
@@ -0,0 +1,206 @@
+import mozunit
+import pytest
+
+LINTER = "perfdocs"
+
+testdata = [
+ {
+ "table_specifications": {
+ "title": ["not a string"],
+ "widths": [10, 10, 10, 10],
+ "header_rows": 1,
+ "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]],
+ "indent": 2,
+ },
+ "error_msg": "TableBuilder attribute title must be a string.",
+ },
+ {
+ "table_specifications": {
+ "title": "I've got a lovely bunch of coconuts",
+ "widths": ("not", "a", "list"),
+ "header_rows": 1,
+ "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]],
+ "indent": 2,
+ },
+ "error_msg": "TableBuilder attribute widths must be a list of integers.",
+ },
+ {
+ "table_specifications": {
+ "title": "There they are all standing in a row",
+ "widths": ["not an integer"],
+ "header_rows": 1,
+ "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]],
+ "indent": 2,
+ },
+ "error_msg": "TableBuilder attribute widths must be a list of integers.",
+ },
+ {
+ "table_specifications": {
+ "title": "Big ones, small ones",
+ "widths": [10, 10, 10, 10],
+ "header_rows": "not an integer",
+ "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]],
+ "indent": 2,
+ },
+ "error_msg": "TableBuilder attribute header_rows must be an integer.",
+ },
+ {
+ "table_specifications": {
+ "title": "Some as big as your head!",
+ "widths": [10, 10, 10, 10],
+ "header_rows": 1,
+ "headers": ("not", "a", "list"),
+ "indent": 2,
+ },
+ "error_msg": "TableBuilder attribute headers must be a two-dimensional list of strings.",
+ },
+ {
+ "table_specifications": {
+ "title": "(And bigger)",
+ "widths": [10, 10, 10, 10],
+ "header_rows": 1,
+ "headers": ["not", "two", "dimensional"],
+ "indent": 2,
+ },
+ "error_msg": "TableBuilder attribute headers must be a two-dimensional list of strings.",
+ },
+ {
+ "table_specifications": {
+ "title": "Give 'em a twist, a flick of the wrist'",
+ "widths": [10, 10, 10, 10],
+ "header_rows": 1,
+ "headers": [[1, 2, 3]],
+ "indent": 2,
+ },
+ "error_msg": "TableBuilder attribute headers must be a two-dimensional list of strings.",
+ },
+ {
+ "table_specifications": {
+ "title": "That's what the showman said!",
+ "widths": [10, 10, 10, 10],
+ "header_rows": 1,
+ "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]],
+ "indent": "not an integer",
+ },
+ "error_msg": "TableBuilder attribute indent must be an integer.",
+ },
+]
+
+table_specifications = {
+ "title": "I've got a lovely bunch of coconuts",
+ "widths": [10, 10, 10],
+ "header_rows": 1,
+ "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]],
+ "indent": 2,
+}
+
+
+@pytest.mark.parametrize("testdata", testdata)
+def test_table_builder_invalid_attributes(testdata):
+ from perfdocs.doc_helpers import TableBuilder
+
+ table_specifications = testdata["table_specifications"]
+ error_msg = testdata["error_msg"]
+
+ with pytest.raises(TypeError) as error:
+ TableBuilder(
+ table_specifications["title"],
+ table_specifications["widths"],
+ table_specifications["header_rows"],
+ table_specifications["headers"],
+ table_specifications["indent"],
+ )
+
+ assert str(error.value) == error_msg
+
+
+def test_table_builder_mismatched_columns():
+ from perfdocs.doc_helpers import MismatchedRowLengthsException, TableBuilder
+
+ table_specifications = {
+ "title": "I've got a lovely bunch of coconuts",
+ "widths": [10, 10, 10, 42],
+ "header_rows": 1,
+ "headers": [["Coconut 1", "Coconut 2", "Coconut 3"]],
+ "indent": 2,
+ }
+
+ with pytest.raises(MismatchedRowLengthsException) as error:
+ TableBuilder(
+ table_specifications["title"],
+ table_specifications["widths"],
+ table_specifications["header_rows"],
+ table_specifications["headers"],
+ table_specifications["indent"],
+ )
+ assert (
+ str(error.value)
+ == "Number of table headers must match number of column widths."
+ )
+
+
+def test_table_builder_add_row_too_long():
+ from perfdocs.doc_helpers import MismatchedRowLengthsException, TableBuilder
+
+ table = TableBuilder(
+ table_specifications["title"],
+ table_specifications["widths"],
+ table_specifications["header_rows"],
+ table_specifications["headers"],
+ table_specifications["indent"],
+ )
+ with pytest.raises(MismatchedRowLengthsException) as error:
+ table.add_row(
+ ["big ones", "small ones", "some as big as your head!", "(and bigger)"]
+ )
+ assert (
+ str(error.value)
+ == "Number of items in a row must must number of columns defined."
+ )
+
+
+def test_table_builder_add_rows_type_error():
+ from perfdocs.doc_helpers import TableBuilder
+
+ table = TableBuilder(
+ table_specifications["title"],
+ table_specifications["widths"],
+ table_specifications["header_rows"],
+ table_specifications["headers"],
+ table_specifications["indent"],
+ )
+ with pytest.raises(TypeError) as error:
+ table.add_rows(
+ ["big ones", "small ones", "some as big as your head!", "(and bigger)"]
+ )
+ assert str(error.value) == "add_rows() requires a two-dimensional list of strings."
+
+
+def test_table_builder_validate():
+ from perfdocs.doc_helpers import TableBuilder
+
+ table = TableBuilder(
+ table_specifications["title"],
+ table_specifications["widths"],
+ table_specifications["header_rows"],
+ table_specifications["headers"],
+ table_specifications["indent"],
+ )
+ table.add_row(["big ones", "small ones", "some as big as your head!"])
+ table.add_row(
+ ["Give 'em a twist", "A flick of the wrist", "That's what the showman said!"]
+ )
+ table = table.finish_table()
+ print(table)
+ assert (
+ table == " .. list-table:: **I've got a lovely bunch of coconuts**\n"
+ " :widths: 10 10 10\n :header-rows: 1\n\n"
+ " * - **Coconut 1**\n - Coconut 2\n - Coconut 3\n"
+ " * - **big ones**\n - small ones\n - some as big as your head!\n"
+ " * - **Give 'em a twist**\n - A flick of the wrist\n"
+ " - That's what the showman said!\n\n"
+ )
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_pylint.py b/tools/lint/test/test_pylint.py
new file mode 100644
index 0000000000..6ee2217089
--- /dev/null
+++ b/tools/lint/test/test_pylint.py
@@ -0,0 +1,24 @@
+import mozunit
+
+LINTER = "pylint"
+
+
+def test_lint_single_file(lint, paths):
+ results = lint(paths("bad.py"))
+ assert len(results) == 3
+ assert results[1].rule == "E0602"
+ assert results[2].rule == "W0101"
+ assert results[2].lineno == 5
+
+ # run lint again to make sure the previous results aren't counted twice
+ results = lint(paths("bad.py"))
+ assert len(results) == 3
+
+
+def test_lint_single_file_good(lint, paths):
+ results = lint(paths("good.py"))
+ assert len(results) == 0
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_rst.py b/tools/lint/test/test_rst.py
new file mode 100644
index 0000000000..e540081a94
--- /dev/null
+++ b/tools/lint/test/test_rst.py
@@ -0,0 +1,25 @@
+import mozunit
+import pytest
+from mozfile import which
+
+LINTER = "rst"
+pytestmark = pytest.mark.skipif(
+ not which("rstcheck"), reason="rstcheck is not installed"
+)
+
+
+def test_basic(lint, paths):
+ results = lint(paths())
+ assert len(results) == 2
+
+ assert "Title underline too short" in results[0].message
+ assert results[0].level == "error"
+ assert results[0].relpath == "bad.rst"
+
+ assert "Title overline & underline mismatch" in results[1].message
+ assert results[1].level == "error"
+ assert results[1].relpath == "bad2.rst"
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_rustfmt.py b/tools/lint/test/test_rustfmt.py
new file mode 100644
index 0000000000..c176a2695d
--- /dev/null
+++ b/tools/lint/test/test_rustfmt.py
@@ -0,0 +1,70 @@
+import mozunit
+
+LINTER = "rustfmt"
+fixed = 0
+
+
+def test_good(lint, config, paths):
+ results = lint(paths("subdir/good.rs"))
+ print(results)
+ assert len(results) == 0
+
+
+def test_basic(lint, config, paths):
+ results = lint(paths("subdir/bad.rs"))
+ print(results)
+ assert len(results) >= 1
+
+ assert "Reformat rust" in results[0].message
+ assert results[0].level == "warning"
+ assert results[0].lineno == 4
+ assert "bad.rs" in results[0].path
+ assert "Print text to the console" in results[0].diff
+
+
+def test_dir(lint, config, paths):
+ results = lint(paths("subdir/"))
+ print(results)
+ assert len(results) >= 4
+
+ assert "Reformat rust" in results[0].message
+ assert results[0].level == "warning"
+ assert results[0].lineno == 4
+ assert "bad.rs" in results[0].path
+ assert "Print text to the console" in results[0].diff
+
+ assert "Reformat rust" in results[1].message
+ assert results[1].level == "warning"
+ assert results[1].lineno == 4
+ assert "bad2.rs" in results[1].path
+ assert "Print text to the console" in results[1].diff
+
+
+def test_fix(lint, create_temp_file):
+
+ contents = """fn main() {
+ // Statements here are executed when the compiled binary is called
+
+ // Print text to the console
+ println!("Hello World!");
+ let mut a;
+ let mut b=1;
+ let mut vec = Vec::new();
+ vec.push(1);
+ vec.push(2);
+
+
+ for x in 5..10 - 5 {
+ a = x;
+ }
+
+ }
+"""
+
+ path = create_temp_file(contents, "bad.rs")
+ lint([path], fix=True)
+ assert fixed == 3
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_shellcheck.py b/tools/lint/test/test_shellcheck.py
new file mode 100644
index 0000000000..1b41e298bd
--- /dev/null
+++ b/tools/lint/test/test_shellcheck.py
@@ -0,0 +1,26 @@
+import mozunit
+import pytest
+from mozfile import which
+
+LINTER = "shellcheck"
+pytestmark = pytest.mark.skipif(
+ not which("shellcheck"), reason="shellcheck is not installed"
+)
+
+
+def test_basic(lint, paths):
+ results = lint(paths())
+ print(results)
+ assert len(results) == 2
+
+ assert "hello appears unused" in results[0].message
+ assert results[0].level == "error"
+ assert results[0].relpath == "bad.sh"
+
+ assert "Double quote to prevent" in results[1].message
+ assert results[1].level == "error"
+ assert results[1].relpath == "bad.sh"
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_trojan_source.py b/tools/lint/test/test_trojan_source.py
new file mode 100644
index 0000000000..64a3789c37
--- /dev/null
+++ b/tools/lint/test/test_trojan_source.py
@@ -0,0 +1,25 @@
+import mozunit
+
+LINTER = "trojan-source"
+
+
+def test_lint_trojan_source(lint, paths):
+ results = lint(paths())
+ print(results)
+ assert len(results) == 3
+
+ assert "disallowed characters" in results[0].message
+ assert results[0].level == "error"
+ assert "commenting-out.cpp" in results[0].relpath
+
+ assert "disallowed characters" in results[1].message
+ assert results[1].level == "error"
+ assert "early-return.py" in results[1].relpath
+
+ assert "disallowed characters" in results[2].message
+ assert results[2].level == "error"
+ assert "invisible-function.rs" in results[2].relpath
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_updatebot.py b/tools/lint/test/test_updatebot.py
new file mode 100644
index 0000000000..55842e99c0
--- /dev/null
+++ b/tools/lint/test/test_updatebot.py
@@ -0,0 +1,44 @@
+import os
+
+import mozunit
+
+LINTER = "updatebot"
+
+
+def test_basic(lint, paths):
+ results = []
+
+ for p in paths():
+ for (root, dirs, files) in os.walk(p):
+ for f in files:
+ if f == ".yamllint":
+ continue
+
+ filepath = os.path.join(root, f)
+ result = lint(filepath, testing=True)
+ if result:
+ results.append(result)
+
+ assert len(results) == 2
+
+ expected_results = 0
+
+ for r in results:
+ if "no-revision.yaml" in r[0].path:
+ expected_results += 1
+ assert "no-revision.yaml" in r[0].path
+ assert (
+ 'If "vendoring" is present, "revision" must be present in "origin"'
+ in r[0].message
+ )
+
+ if "cargo-mismatch.yaml" in r[0].path:
+ expected_results += 1
+ assert "cargo-mismatch.yaml" in r[0].path
+ assert "wasn't found in Cargo.lock" in r[0].message
+
+ assert expected_results == 2
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/test/test_yaml.py b/tools/lint/test/test_yaml.py
new file mode 100644
index 0000000000..63b678d152
--- /dev/null
+++ b/tools/lint/test/test_yaml.py
@@ -0,0 +1,28 @@
+import mozunit
+
+LINTER = "yaml"
+
+
+def test_basic(lint, paths):
+ results = lint(paths())
+
+ assert len(results) == 3
+
+ assert "line too long (122 > 80 characters)" in results[0].message
+ assert results[0].level == "error"
+ assert "bad.yml" in results[0].relpath
+ assert results[0].lineno == 3
+
+ assert "wrong indentation: expected 4 but found 8" in results[1].message
+ assert results[1].level == "error"
+ assert "bad.yml" in results[1].relpath
+ assert results[0].lineno == 3
+
+ assert "could not find expected" in results[2].message
+ assert results[2].level == "error"
+ assert "bad.yml" in results[2].relpath
+ assert results[2].lineno == 9
+
+
+if __name__ == "__main__":
+ mozunit.main()
diff --git a/tools/lint/tox/tox_requirements.txt b/tools/lint/tox/tox_requirements.txt
new file mode 100644
index 0000000000..4ec4191443
--- /dev/null
+++ b/tools/lint/tox/tox_requirements.txt
@@ -0,0 +1,7 @@
+pluggy==0.13.1 --hash=sha256:966c145cd83c96502c3c3868f50408687b38434af77734af1e9ca461a4081d2d
+importlib-metadata==0.23 --hash=sha256:d5f18a79777f3aa179c145737780282e27b508fc8fd688cb17c7a813e8bd39af
+more-itertools==7.2.0 --hash=sha256:92b8c4b06dac4f0611c0729b2f2ede52b2e1bac1ab48f089c7ddc12e26bb60c4
+zipp==0.6.0 --hash=sha256:f06903e9f1f43b12d371004b4ac7b06ab39a44adc747266928ae6debfa7b3335
+py==1.5.4 --hash=sha256:e31fb2767eb657cbde86c454f02e99cb846d3cd9d61b318525140214fdc0e98e
+tox==2.7.0 --hash=sha256:0f37ea637ead4a5bbae91531b0bf8fd327c7152e20255e5960ee180598228d21
+virtualenv==15.1.0 --hash=sha256:39d88b533b422825d644087a21e78c45cf5af0ef7a99a1fc9fbb7b481e5c85b0
diff --git a/tools/lint/trojan-source.yml b/tools/lint/trojan-source.yml
new file mode 100644
index 0000000000..c3482a32c7
--- /dev/null
+++ b/tools/lint/trojan-source.yml
@@ -0,0 +1,28 @@
+---
+trojan-source:
+ description: Trojan Source attack - CVE-2021-42572
+ include:
+ - .
+ exclude:
+ - intl/lwbrk/rulebrk.c
+ - testing/web-platform/tests/conformance-checkers/tools/ins-del-datetime.py
+ - gfx/skia/skia/src/sksl/SkSLByteCodeGenerator.cpp
+ - modules/freetype2/src/autofit/afblue.c
+ - modules/freetype2/builds/amiga/include/config/ftconfig.h
+ - modules/freetype2/builds/amiga/include/config/ftmodule.h
+ - modules/freetype2/builds/amiga/src/base/ftsystem.c
+ - third_party/rust/chardetng/src/data.rs
+ - third_party/rust/error-chain/tests/tests.rs
+ - third_party/rust/unicode-width/src/tests.rs
+ - security/nss/gtests/mozpkix_gtest/pkixnames_tests.cpp
+ extensions:
+ - .c
+ - .cc
+ - .cpp
+ - .h
+ - .py
+ - .rs
+ support-files:
+ - 'tools/lint/trojan-source/**'
+ type: external
+ payload: trojan-source:lint
diff --git a/tools/lint/trojan-source/__init__.py b/tools/lint/trojan-source/__init__.py
new file mode 100644
index 0000000000..a20c10203d
--- /dev/null
+++ b/tools/lint/trojan-source/__init__.py
@@ -0,0 +1,67 @@
+# 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 sys
+import unicodedata
+
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+# Code inspired by Red Hat
+# https://github.com/siddhesh/find-unicode-control/
+# published under the 'BSD 3-Clause' license
+# https://access.redhat.com/security/vulnerabilities/RHSB-2021-007
+
+results = []
+
+disallowed = set(
+ chr(c) for c in range(sys.maxunicode) if unicodedata.category(chr(c)) == "Cf"
+)
+
+
+def getfiletext(config, filename):
+ # Make a text string from a file, attempting to decode from latin1 if necessary.
+ # Other non-utf-8 locales are not supported at the moment.
+ with open(filename, "rb") as infile:
+ try:
+ return infile.read().decode("utf-8")
+ except Exception as e:
+ res = {
+ "path": filename,
+ "message": "Could not open file as utf-8 - maybe an encoding error: %s"
+ % e,
+ "level": "error",
+ }
+ results.append(result.from_config(config, **res))
+ return None
+
+ return None
+
+
+def analyze_text(filename, text, disallowed):
+ line = 0
+ for t in text.splitlines():
+ line = line + 1
+ subset = [c for c in t if chr(ord(c)) in disallowed]
+ if subset:
+ return (subset, line)
+
+ return ("", 0)
+
+
+def lint(paths, config, **lintargs):
+ files = list(expand_exclusions(paths, config, lintargs["root"]))
+ for f in files:
+ text = getfiletext(config, f)
+ if text:
+ (subset, line) = analyze_text(f, text, disallowed)
+ if subset:
+ res = {
+ "path": f,
+ "lineno": line,
+ "message": "disallowed characters: %s" % subset,
+ "level": "error",
+ }
+ results.append(result.from_config(config, **res))
+
+ return {"results": results, "fixed": 0}
diff --git a/tools/lint/updatebot.yml b/tools/lint/updatebot.yml
new file mode 100644
index 0000000000..90f13e8cf4
--- /dev/null
+++ b/tools/lint/updatebot.yml
@@ -0,0 +1,9 @@
+---
+updatebot:
+ description: >
+ "Ensure moz.yaml files are valid"
+ extensions: ['yaml']
+ include: ['.']
+ type: external-file
+ level: error
+ payload: updatebot.validate_yaml:lint
diff --git a/tools/lint/updatebot/__init__.py b/tools/lint/updatebot/__init__.py
new file mode 100644
index 0000000000..c580d191c1
--- /dev/null
+++ b/tools/lint/updatebot/__init__.py
@@ -0,0 +1,3 @@
+# 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/.
diff --git a/tools/lint/updatebot/validate_yaml.py b/tools/lint/updatebot/validate_yaml.py
new file mode 100644
index 0000000000..802a9033fa
--- /dev/null
+++ b/tools/lint/updatebot/validate_yaml.py
@@ -0,0 +1,52 @@
+# 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/.
+
+from mozbuild.vendor.moz_yaml import load_moz_yaml
+from mozlint import result
+from mozlint.pathutils import expand_exclusions
+
+
+class UpdatebotValidator:
+ def lint_file(self, path, **kwargs):
+ if not kwargs.get("testing", False) and not path.endswith("moz.yaml"):
+ # When testing, process all files provided
+ return None
+ if not kwargs.get("testing", False) and "test/files/updatebot" in path:
+ # When not testing, ignore the test files
+ return None
+
+ try:
+ yaml = load_moz_yaml(path)
+
+ if "vendoring" in yaml and yaml["vendoring"].get("flavor", None) == "rust":
+ yaml_revision = yaml["origin"]["revision"]
+
+ with open("Cargo.lock", "r") as f:
+ for line in f:
+ if yaml_revision in line:
+ return None
+
+ return f"Revision {yaml_revision} specified in {path} wasn't found in Cargo.lock"
+
+ return None
+ except Exception as e:
+ return f"Could not load {path} according to schema in moz_yaml.py: {e}"
+
+
+def lint(paths, config, **lintargs):
+ # expand_exclusions expects a list, and will convert a string
+ # into it if it doesn't receive one
+ if not isinstance(paths, list):
+ paths = [paths]
+
+ errors = []
+ files = list(expand_exclusions(paths, config, lintargs["root"]))
+
+ m = UpdatebotValidator()
+ for f in files:
+ message = m.lint_file(f, **lintargs)
+ if message:
+ errors.append(result.from_config(config, path=f, message=message))
+
+ return errors
diff --git a/tools/lint/wpt.yml b/tools/lint/wpt.yml
new file mode 100644
index 0000000000..dd3c0dd042
--- /dev/null
+++ b/tools/lint/wpt.yml
@@ -0,0 +1,10 @@
+---
+wpt:
+ description: web-platform-tests lint
+ include:
+ - testing/web-platform/tests
+ exclude: []
+ support-files:
+ - tools/lint/wpt/wpt.py
+ type: external
+ payload: wpt.wpt:lint
diff --git a/tools/lint/wpt/__init__.py b/tools/lint/wpt/__init__.py
new file mode 100644
index 0000000000..c580d191c1
--- /dev/null
+++ b/tools/lint/wpt/__init__.py
@@ -0,0 +1,3 @@
+# 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/.
diff --git a/tools/lint/wpt/wpt.py b/tools/lint/wpt/wpt.py
new file mode 100644
index 0000000000..1bbb0a6a65
--- /dev/null
+++ b/tools/lint/wpt/wpt.py
@@ -0,0 +1,59 @@
+# -*- Mode: python; indent-tabs-mode: nil; tab-width: 40 -*-
+# vim: set filetype=python:
+# 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 json
+import os
+import sys
+
+from mozlint import result
+from mozprocess import ProcessHandler
+
+results = []
+
+
+def lint(files, config, **kwargs):
+ log = kwargs["log"]
+ tests_dir = os.path.join(kwargs["root"], "testing", "web-platform", "tests")
+
+ def process_line(line):
+ try:
+ data = json.loads(line)
+ except ValueError:
+ return
+
+ data["level"] = "error"
+ data["path"] = os.path.relpath(
+ os.path.join(tests_dir, data["path"]), kwargs["root"]
+ )
+ data.setdefault("lineno", 0)
+ results.append(result.from_config(config, **data))
+
+ if files == [tests_dir]:
+ print(
+ "No specific files specified, running the full wpt lint" " (this is slow)",
+ file=sys.stderr,
+ )
+ files = ["--all"]
+ cmd = ["python3", os.path.join(tests_dir, "wpt"), "lint", "--json"] + files
+ log.debug("Command: {}".format(" ".join(cmd)))
+
+ proc = ProcessHandler(
+ cmd, env=os.environ, processOutputLine=process_line, universal_newlines=True
+ )
+ proc.run()
+ try:
+ proc.wait()
+ if proc.returncode != 0:
+ results.append(
+ result.from_config(
+ config,
+ message="Lint process exited with return code %s" % proc.returncode,
+ )
+ )
+ except KeyboardInterrupt:
+ proc.kill()
+
+ return results
diff --git a/tools/lint/yaml.yml b/tools/lint/yaml.yml
new file mode 100644
index 0000000000..840ad6eec3
--- /dev/null
+++ b/tools/lint/yaml.yml
@@ -0,0 +1,18 @@
+---
+yamllint:
+ description: YAML linter
+ include:
+ - .cron.yml
+ - .taskcluster.yml
+ - browser/config/
+ - python/mozlint/
+ - security/nss/.taskcluster.yml
+ - taskcluster
+ - testing/mozharness
+ - tools
+ extensions: ['yml', 'yaml']
+ support-files:
+ - '**/.yamllint'
+ - 'tools/lint/yamllint_/**'
+ type: external
+ payload: yamllint_:lint
diff --git a/tools/lint/yamllint_/__init__.py b/tools/lint/yamllint_/__init__.py
new file mode 100644
index 0000000000..2244fabd3f
--- /dev/null
+++ b/tools/lint/yamllint_/__init__.py
@@ -0,0 +1,101 @@
+# 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 re
+import sys
+from collections import defaultdict
+
+from mozbuild.base import MozbuildObject
+
+topsrcdir = MozbuildObject.from_environment().topsrcdir
+
+from mozlint import result
+from mozlint.pathutils import get_ancestors_by_name
+from mozlint.util.implementation import LintProcess
+
+YAMLLINT_FORMAT_REGEX = re.compile("(.*):(.*):(.*): \[(error|warning)\] (.*) \((.*)\)$")
+
+results = []
+
+
+class YAMLLintProcess(LintProcess):
+ def process_line(self, line):
+ try:
+ match = YAMLLINT_FORMAT_REGEX.match(line)
+ abspath, line, col, level, message, code = match.groups()
+ except AttributeError:
+ print("Unable to match yaml regex against output: {}".format(line))
+ return
+
+ res = {
+ "path": os.path.relpath(str(abspath), self.config["root"]),
+ "message": str(message),
+ "level": "error",
+ "lineno": line,
+ "column": col,
+ "rule": code,
+ }
+
+ results.append(result.from_config(self.config, **res))
+
+
+def get_yamllint_version():
+ from yamllint import APP_VERSION
+
+ return APP_VERSION
+
+
+def run_process(config, cmd):
+ proc = YAMLLintProcess(config, cmd)
+ proc.run()
+ try:
+ proc.wait()
+ except KeyboardInterrupt:
+ proc.kill()
+
+
+def gen_yamllint_args(cmdargs, paths=None, conf_file=None):
+ args = cmdargs[:]
+ if isinstance(paths, str):
+ paths = [paths]
+ if conf_file and conf_file != "default":
+ return args + ["-c", conf_file] + paths
+ return args + paths
+
+
+def lint(files, config, **lintargs):
+ log = lintargs["log"]
+
+ log.debug("Version: {}".format(get_yamllint_version()))
+
+ cmdargs = [
+ sys.executable,
+ os.path.join(topsrcdir, "mach"),
+ "python",
+ "--",
+ "-m",
+ "yamllint",
+ "-f",
+ "parsable",
+ ]
+ log.debug("Command: {}".format(" ".join(cmdargs)))
+
+ config = config.copy()
+ config["root"] = lintargs["root"]
+
+ # Run any paths with a .yamllint file in the directory separately so
+ # it gets picked up. This means only .yamllint files that live in
+ # directories that are explicitly included will be considered.
+ paths_by_config = defaultdict(list)
+ for f in files:
+ conf_files = get_ancestors_by_name(".yamllint", f, config["root"])
+ paths_by_config[conf_files[0] if conf_files else "default"].append(f)
+
+ for conf_file, paths in paths_by_config.items():
+ run_process(
+ config, gen_yamllint_args(cmdargs, conf_file=conf_file, paths=paths)
+ )
+
+ return results