diff options
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.js | 180 |
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, +}; |