summaryrefslogtreecommitdiffstats
path: root/tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/environments
diff options
context:
space:
mode:
Diffstat (limited to 'tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/environments')
-rw-r--r--tools/lint/eslint/eslint-plugin-spidermonkey-js/lib/environments/self-hosted.js180
1 files changed, 180 insertions, 0 deletions
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,
+};