From 5f8de423f190bbb79a62f804151bc24824fa32d8 Mon Sep 17 00:00:00 2001 From: "Matt A. Tobin" Date: Fri, 2 Feb 2018 04:16:08 -0500 Subject: Add m-esr52 at 52.6.0 --- .../eslint/eslint-plugin-mozilla/lib/globals.js | 188 ++++++++ .../eslint/eslint-plugin-mozilla/lib/helpers.js | 524 +++++++++++++++++++++ .../lint/eslint/eslint-plugin-mozilla/lib/index.js | 45 ++ .../lib/processors/xbl-bindings.js | 363 ++++++++++++++ .../eslint-plugin-mozilla/lib/rules/.eslintrc.js | 51 ++ .../lib/rules/balanced-listeners.js | 113 +++++ .../lib/rules/import-browserjs-globals.js | 83 ++++ .../lib/rules/import-globals.js | 15 + .../lib/rules/import-headjs-globals.js | 49 ++ .../lib/rules/mark-test-function-used.js | 37 ++ .../eslint-plugin-mozilla/lib/rules/no-aArgs.js | 55 +++ .../lib/rules/no-cpows-in-tests.js | 112 +++++ .../lib/rules/no-single-arg-cu-import.js | 39 ++ .../lib/rules/reject-importGlobalProperties.js | 37 ++ .../lib/rules/reject-some-requires.js | 48 ++ .../lib/rules/var-only-at-top-level.js | 34 ++ 16 files changed, 1793 insertions(+) create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/index.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-headjs-globals.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js create mode 100644 tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js (limited to 'tools/lint/eslint/eslint-plugin-mozilla/lib') 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 000000000..acbfa0684 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/globals.js @@ -0,0 +1,188 @@ +/** + * @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 escope = require("escope"); +const estraverse = require("estraverse"); + +/** + * 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 = undefined; + if (pos !== -1) { + value = name.substring(pos + 1, name.length); + name = name.substring(0, pos); + } + + items[name] = { + value: (value === "true"), + comment: 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(); + +/** + * 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} path + * The absolute path of the file being parsed. + */ +function GlobalsForNode(path) { + this.path = path; + this.root = helpers.getRootDir(path); +} + +GlobalsForNode.prototype = { + BlockComment(node, parents) { + let value = node.value.trim(); + let match = /^import-globals-from\s+(.+)$/.exec(value); + if (!match) { + return []; + } + + let filePath = match[1].trim(); + + if (!path.isAbsolute(filePath)) { + let dirName = path.dirname(this.path); + filePath = path.resolve(dirName, filePath); + } + + return module.exports.getGlobalsForFile(filePath); + }, + + ExpressionStatement(node, parents) { + let isGlobal = helpers.getIsGlobalScope(parents); + let names = helpers.convertExpressionToGlobals(node, isGlobal, this.root); + return names.map(name => { return { name, writable: true }}); + }, +}; + +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} path + * The absolute path of the file to be parsed. + */ + getGlobalsForFile(path) { + if (globalCache.has(path)) { + return globalCache.get(path); + } + + let content = fs.readFileSync(path, "utf8"); + + // Parse the content into an AST + let ast = helpers.getAST(content); + + // Discover global declarations + let scopeManager = escope.analyze(ast); + 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(path); + + helpers.walkAST(ast, (type, node, parents) => { + // We have to discover any globals that ESLint would have defined through + // comment directives + if (type == "BlockComment") { + let value = node.value.trim(); + 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 + }) + } + } + } + + if (type in handler) { + let newGlobals = handler[type](node, parents); + globals.push.apply(globals, newGlobals); + } + }); + + globalCache.set(path, globals); + + return 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(); + } + }; + + // Install thin wrappers around GlobalsForNode + let handler = new GlobalsForNode(helpers.getAbsoluteFilePath(context)); + + for (let type of Object.keys(GlobalsForNode.prototype)) { + parser[type] = function(node) { + let globals = handler[type](node, context.getAncestors()); + helpers.addGlobals(globals, globalScope); + } + } + + 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 000000000..50e00ab97 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/helpers.js @@ -0,0 +1,524 @@ +/** + * @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"; + +var escope = require("escope"); +var espree = require("espree"); +var estraverse = require("estraverse"); +var path = require("path"); +var fs = require("fs"); +var ini = require("ini-parser"); + +var modules = null; +var directoryManifests = new Map(); + +var definitions = [ + /^loader\.lazyGetter\(this, "(\w+)"/, + /^loader\.lazyImporter\(this, "(\w+)"/, + /^loader\.lazyServiceGetter\(this, "(\w+)"/, + /^loader\.lazyRequireGetter\(this, "(\w+)"/, + /^XPCOMUtils\.defineLazyGetter\(this, "(\w+)"/, + /^XPCOMUtils\.defineLazyModuleGetter\(this, "(\w+)"/, + /^XPCOMUtils\.defineLazyServiceGetter\(this, "(\w+)"/, + /^XPCOMUtils\.defineConstant\(this, "(\w+)"/, + /^DevToolsUtils\.defineLazyModuleGetter\(this, "(\w+)"/, + /^DevToolsUtils\.defineLazyGetter\(this, "(\w+)"/, + /^Object\.defineProperty\(this, "(\w+)"/, + /^Reflect\.defineProperty\(this, "(\w+)"/, + /^this\.__defineGetter__\("(\w+)"/, + /^this\.(\w+) =/ +]; + +var imports = [ + /^(?:Cu|Components\.utils)\.import\(".*\/((.*?)\.jsm?)"(?:, this)?\)/, +]; + +module.exports = { + /** + * Gets the abstract syntax tree (AST) of the JavaScript source code contained + * in sourceText. + * + * @param {String} sourceText + * Text containing valid JavaScript. + * + * @return {Object} + * The resulting AST. + */ + getAST: function(sourceText) { + // Use a permissive config file to allow parsing of anything that Espree + // can parse. + var config = this.getPermissiveConfig(); + + return espree.parse(sourceText, config); + }, + + /** + * 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: function(node) { + switch (node.type) { + case "MemberExpression": + if (node.computed) + throw new Error("getASTSource unsupported computed MemberExpression"); + 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 "ArrowFunctionExpression": + return "() => {}"; + case "AssignmentExpression": + return this.getASTSource(node.left) + " = " + this.getASTSource(node.right); + default: + throw new Error("getASTSource unsupported node type: " + node.type); + } + }, + + /** + * This walks an AST in a manner similar to ESLint passing node and comment + * 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 {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, listener) { + let parents = []; + + let seenComments = new Set(); + function sendCommentEvents(comments) { + if (!comments) { + return; + } + + for (let comment of comments) { + if (seenComments.has(comment)) { + return; + } + seenComments.add(comment); + + listener(comment.type + "Comment", comment, parents); + } + } + + estraverse.traverse(ast, { + enter(node, parent) { + // Comments are held in node.comments for empty programs + let leadingComments = node.leadingComments; + if (node.type === "Program" && node.body.length == 0) { + leadingComments = node.comments; + } + + sendCommentEvents(leadingComments); + listener(node.type, node, parents); + sendCommentEvents(node.trailingComments); + + parents.push(node); + }, + + leave(node, parent) { + // TODO send comment exit events + listener(node.type + ":exit", node, parents); + + if (parents.length == 0) { + throw new Error("Left more nodes than entered."); + } + parents.pop(); + } + }); + 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. + * @param {String} repository + * The root of the repository. + * + * @return {Array} + * An array of variable names defined. + */ + convertExpressionToGlobals: function(node, isGlobal, repository) { + if (!modules) { + modules = require(path.join(repository, "tools", "lint", "eslint", "modules.json")); + } + + try { + var source = this.getASTSource(node); + } + catch (e) { + return []; + } + + for (var reg of definitions) { + var match = source.match(reg); + if (match) { + // Must be in the global scope + if (!isGlobal) { + return []; + } + + return [match[1]]; + } + } + + for (reg of imports) { + var match = source.match(reg); + if (match) { + // The two argument form is only acceptable in the global scope + if (node.expression.arguments.length > 1 && !isGlobal) { + return []; + } + + if (match[1] in modules) { + return modules[match[1]]; + } + + return [match[2]]; + } + } + + 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. + */ + addVarToScope: function(name, scope, writable) { + scope.__defineGeneric(name, scope.set, scope.variables, null, null); + + let variable = scope.set.get(name); + variable.eslintExplicitGlobal = false; + variable.writeable = writable; + + // 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. + */ + addGlobals: function(globalVars, scope) { + globalVars.forEach(v => this.addVarToScope(v.name, scope, v.writable)); + }, + + /** + * To allow espree to parse almost any JavaScript we need as many features as + * possible turned on. This method returns that config. + * + * @return {Object} + * Espree compatible permissive config. + */ + getPermissiveConfig: function() { + return { + range: true, + loc: true, + comment: true, + attachComment: true, + ecmaVersion: 8, + sourceType: "script", + ecmaFeatures: { + experimentalObjectRestSpread: true, + globalReturn: true, + } + }; + }, + + /** + * Check whether the context is the global scope. + * + * @param {Array} ancestors + * The parents of the current node. + * + * @return {Boolean} + * True or false + */ + getIsGlobalScope: function(ancestors) { + for (let parent of ancestors) { + if (parent.type == "FunctionExpression" || + parent.type == "FunctionDeclaration") { + 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(this) + * + * @return {Boolean} + * True or false + */ + getIsHeadFile: function(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(this) + * + * @return {String[]} + * Paths to head files to load for the test + */ + getTestHeadFiles: function(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.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: function(dir) { + if (directoryManifests.has(dir)) { + return directoryManifests.get(dir); + } + + let manifests = []; + + let names = fs.readdirSync(dir); + for (let name of names) { + if (!name.endsWith(".ini")) { + continue; + } + + try { + let manifest = ini.parse(fs.readFileSync(path.join(dir, name), 'utf8')); + + 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(this) + * + * @return {String} + * The path to the test manifest file + */ + getTestManifest: function(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(this) + * + * @return {Boolean} + * True or false + */ + getIsTest: function(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); + }, + + /** + * 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(this) + * + * @return {String or null} + * Test type: xpcshell, browser, chrome, mochitest + */ + getTestType: function(scope) { + let manifest = this.getTestManifest(scope); + if (manifest) { + let name = path.basename(manifest); + for (let testType of ["browser", "xpcshell", "chrome", "mochitest"]) { + 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_")) { + return "xpcshell"; + } + + return null; + }, + + /** + * Gets the root directory of the repository by walking up directories until + * a .eslintignore file is found. + * @param {String} fileName + * The absolute path of a file in the repository + * + * @return {String} The absolute path of the repository directory + */ + getRootDir: function(fileName) { + var dirName = path.dirname(fileName); + + while (dirName && !fs.existsSync(path.join(dirName, ".eslintignore"))) { + dirName = path.dirname(dirName); + } + + if (!dirName) { + throw new Error("Unable to find root of repository"); + } + + return dirName; + }, + + /** + * 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: function(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); + } else { + // 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: function(path) { + return path.replace(/^"/, "").replace(/"$/, ""); + } +}; 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 000000000..e1f694c36 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/index.js @@ -0,0 +1,45 @@ +/** + * @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 = { + processors: { + ".xml": require("../lib/processors/xbl-bindings"), + }, + rules: { + "balanced-listeners": require("../lib/rules/balanced-listeners"), + "import-globals": require("../lib/rules/import-globals"), + "import-headjs-globals": require("../lib/rules/import-headjs-globals"), + "import-browserjs-globals": require("../lib/rules/import-browserjs-globals"), + "mark-test-function-used": require("../lib/rules/mark-test-function-used"), + "no-aArgs": require("../lib/rules/no-aArgs"), + "no-cpows-in-tests": require("../lib/rules/no-cpows-in-tests"), + "no-single-arg-cu-import": require("../lib/rules/no-single-arg-cu-import"), + "reject-importGlobalProperties": require("../lib/rules/reject-importGlobalProperties"), + "reject-some-requires": require("../lib/rules/reject-some-requires"), + "var-only-at-top-level": require("../lib/rules/var-only-at-top-level") + }, + rulesConfig: { + "balanced-listeners": 0, + "import-globals": 0, + "import-headjs-globals": 0, + "import-browserjs-globals": 0, + "mark-test-function-used": 0, + "no-aArgs": 0, + "no-cpows-in-tests": 0, + "no-single-arg-cu-import": 0, + "reject-importGlobalProperties": 0, + "reject-some-requires": 0, + "var-only-at-top-level": 0 + } +}; diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js new file mode 100644 index 000000000..dc09550f2 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/processors/xbl-bindings.js @@ -0,0 +1,363 @@ +/** + * @fileoverview Converts functions and handlers from XBL bindings into JS + * 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 NS_XBL = "http://www.mozilla.org/xbl"; + +let sax = require("sax"); + +// Converts sax's error message to something that eslint will understand +let errorRegex = /(.*)\nLine: (\d+)\nColumn: (\d+)\nChar: (.*)/ +function parseError(err) { + let matches = err.message.match(errorRegex); + if (!matches) + return null; + + return { + fatal: true, + message: matches[1], + line: parseInt(matches[2]) + 1, + column: parseInt(matches[3]) + } +} + +let entityRegex = /&[\w][\w-\.]*;/g; + +// A simple sax listener that generates a tree of element information +function XMLParser(parser) { + this.parser = parser; + parser.onopentag = this.onOpenTag.bind(this); + parser.onclosetag = this.onCloseTag.bind(this); + parser.ontext = this.onText.bind(this); + parser.onopencdata = this.onOpenCDATA.bind(this); + parser.oncdata = this.onCDATA.bind(this); + parser.oncomment = this.onComment.bind(this); + + this.document = { + local: "#document", + uri: null, + children: [], + comments: [], + } + this._currentNode = this.document; +} + +XMLParser.prototype = { + parser: null, + + onOpenTag: function(tag) { + let node = { + parentNode: this._currentNode, + local: tag.local, + namespace: tag.uri, + attributes: {}, + children: [], + comments: [], + textContent: "", + textLine: this.parser.line, + textColumn: this.parser.column, + textEndLine: this.parser.line + } + + for (let attr of Object.keys(tag.attributes)) { + if (tag.attributes[attr].uri == "") { + node.attributes[attr] = tag.attributes[attr].value; + } + } + + this._currentNode.children.push(node); + this._currentNode = node; + }, + + onCloseTag: function(tagname) { + this._currentNode.textEndLine = this.parser.line; + this._currentNode = this._currentNode.parentNode; + }, + + addText: function(text) { + this._currentNode.textContent += text; + }, + + onText: function(text) { + // Replace entities with some valid JS token. + this.addText(text.replace(entityRegex, "null")); + }, + + onOpenCDATA: function() { + // Turn the CDATA opening tag into whitespace for indent alignment + this.addText(" ".repeat(" s.trim().length > 0) + .map(s => s.length - s.trimLeft().length); + // Find the smallest indent level in use + let minIndent = Math.min.apply(null, indents); + + for (let line of lines) { + if (line.trim().length == 0) { + // Don't offset lines that are only whitespace, the only possible JS error + // is trailing whitespace and we want it to point at the right place + lineMap[scriptLines.length] = { line: startLine, offset: 0 }; + } else { + line = " ".repeat(reindent * INDENT_LEVEL) + line.substring(minIndent); + lineMap[scriptLines.length] = { line: startLine, offset: reindent * INDENT_LEVEL - (minIndent - 1) }; + } + + scriptLines.push(line); + startLine++; + } +} + +module.exports = { + preprocess: function(text, filename) { + xmlParseError = null; + scriptLines = []; + lineMap = []; + + // Non-strict allows us to ignore many errors from entities and + // preprocessing at the expense of failing to report some XML errors. + // Unfortunately it also throws away the case of tagnames and attributes + let parser = sax.parser(false, { + lowercase: true, + xmlns: true, + }); + + parser.onerror = function(err) { + xmlParseError = parseError(err); + } + + let xp = new XMLParser(parser); + parser.write(text); + + // Sanity checks to make sure we're dealing with an XBL document + let document = xp.document; + if (document.children.length != 1) { + return []; + } + + let bindings = document.children[0]; + if (bindings.local != "bindings" || bindings.namespace != NS_XBL) { + return []; + } + + for (let comment of document.comments) { + addSyntheticLine(`/*`, 0, true); + for (let line of comment.split("\n")) { + addSyntheticLine(`${line.trim()}`, 0, true); + } + addSyntheticLine(`*/`, 0, true); + } + + addSyntheticLine(`this.bindings = {`, bindings.textLine); + + for (let binding of bindings.children) { + if (binding.local != "binding" || binding.namespace != NS_XBL) { + continue; + } + + addSyntheticLine(indent(1) + `"${binding.attributes.id}": {`, binding.textLine); + + for (let part of binding.children) { + if (part.namespace != NS_XBL) { + continue; + } + + if (part.local == "implementation") { + addSyntheticLine(indent(2) + `implementation: {`, part.textLine); + } else if (part.local == "handlers") { + addSyntheticLine(indent(2) + `handlers: [`, part.textLine); + } else { + continue; + } + + for (let item of part.children) { + if (item.namespace != NS_XBL) { + continue; + } + + switch (item.local) { + case "field": { + // Fields are something like lazy getter functions + + // Ignore empty fields + if (item.textContent.trim().length == 0) { + continue; + } + + addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, item.textLine); + addSyntheticLine(indent(4) + `return (`, item.textLine); + + // Remove trailing semicolons, as we are adding our own + item.textContent = item.textContent.replace(/;(?=\s*$)/, ""); + addNodeLines(item, 5); + + addSyntheticLine(indent(4) + `);`, item.textLine); + addSyntheticLine(indent(3) + `},`, item.textEndLine); + break; + } + case "constructor": + case "destructor": { + // Constructors and destructors become function declarations + addSyntheticLine(indent(3) + `${item.local}() {`, item.textLine); + addNodeLines(item, 4); + addSyntheticLine(indent(3) + `},`, item.textEndLine); + break; + } + case "method": { + // Methods become function declarations with the appropriate params + + let params = item.children.filter(n => n.local == "parameter" && n.namespace == NS_XBL) + .map(n => n.attributes.name) + .join(", "); + let body = item.children.filter(n => n.local == "body" && n.namespace == NS_XBL)[0]; + + addSyntheticLine(indent(3) + `${item.attributes.name}(${params}) {`, item.textLine); + addNodeLines(body, 4); + addSyntheticLine(indent(3) + `},`, item.textEndLine); + break; + } + case "property": { + // Properties become one or two function declarations + for (let propdef of item.children) { + if (propdef.namespace != NS_XBL) { + continue; + } + + if (propdef.local == "setter") { + addSyntheticLine(indent(3) + `set ${item.attributes.name}(val) {`, propdef.textLine); + } else if (propdef.local == "getter") { + addSyntheticLine(indent(3) + `get ${item.attributes.name}() {`, propdef.textLine); + } else { + continue; + } + addNodeLines(propdef, 4); + addSyntheticLine(indent(3) + `},`, propdef.textEndLine); + } + break; + } + case "handler": { + // Handlers become a function declaration with an `event` parameter + addSyntheticLine(indent(3) + `function(event) {`, item.textLine); + addNodeLines(item, 4); + addSyntheticLine(indent(3) + `},`, item.textEndLine); + break; + } + default: + continue; + } + } + + addSyntheticLine(indent(2) + (part.local == "implementation" ? `},` : `],`), part.textEndLine); + } + addSyntheticLine(indent(1) + `},`, binding.textEndLine); + } + addSyntheticLine(`};`, bindings.textEndLine); + + let script = scriptLines.join("\n") + "\n"; + return [script]; + }, + + postprocess: function(messages, filename) { + // If there was an XML parse error then just return that + if (xmlParseError) { + return [xmlParseError]; + } + + // For every message from every script block update the line to point to the + // correct place. + let errors = []; + for (let i = 0; i < messages.length; i++) { + for (let message of messages[i]) { + // ESLint indexes lines starting at 1 but our arrays start at 0 + let mapped = lineMap[message.line - 1]; + + message.line = mapped.line + 1; + if (mapped.offset) { + message.column -= mapped.offset; + } else { + message.column = NaN; + } + + errors.push(message); + } + } + + return errors; + } +}; diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js new file mode 100644 index 000000000..505a3ea82 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/.eslintrc.js @@ -0,0 +1,51 @@ +"use strict"; + +/** + * Based on npm coding standards at https://docs.npmjs.com/misc/coding-style. + * + * The places we differ from the npm coding style: + * - Commas should be at the end of a line. + * - Always use semicolons. + * - Functions should not have whitespace before params. + */ + +module.exports = { + "env": { + "node": true + }, + + "rules": { + "brace-style": ["error", "1tbs"], + "camelcase": "error", + "comma-dangle": ["error", "never"], + "comma-spacing": "error", + "comma-style": ["error", "last"], + "curly": ["error", "multi-line"], + "handle-callback-err": ["error", "er"], + "indent": ["error", 2, {"SwitchCase": 1}], + "max-len": ["error", 80, "error"], + "no-multiple-empty-lines": ["error", {"max": 1}], + "no-undef": "error", + "no-undef-init": "error", + "no-unexpected-multiline": "error", + "object-curly-spacing": "off", + "one-var": ["error", "never"], + "operator-linebreak": ["error", "after"], + "semi": ["error", "always"], + "space-before-blocks": "error", + "space-before-function-paren": ["error", "never"], + "keyword-spacing": "error", + "strict": ["error", "global"], + }, + + // Globals accessible within node modules. + "globals": { + "DTRACE_HTTP_CLIENT_REQUEST": true, + "DTRACE_HTTP_CLIENT_RESPONSE": true, + "DTRACE_HTTP_SERVER_REQUEST": true, + "DTRACE_HTTP_SERVER_RESPONSE": true, + "DTRACE_NET_SERVER_CONNECTION": true, + "DTRACE_NET_STREAM_END": true, + "Intl": true, + }, +}; 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 000000000..c658a6b44 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/balanced-listeners.js @@ -0,0 +1,113 @@ +/** + * @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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +module.exports = function(context) { + // --------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------- + + 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) { + addedListeners.push({ + functionName: node.callee.property.name, + type: node.arguments[0].value, + node: node.callee.property, + useCapture: node.arguments[2] ? node.arguments[2].value : null + }); + } + + function addRemovedListener(node) { + removedListeners.push({ + functionName: node.callee.property.name, + type: node.arguments[0].value, + useCapture: node.arguments[2] ? node.arguments[2].value : null + }); + } + + 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; + } + + // --------------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------------- + + return { + CallExpression: function(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(listener.node, + "No corresponding '{{functionName}}({{type}})' was found.", + { + functionName: DICTIONARY[listener.functionName], + type: listener.type + }); + }); + } + }; +}; diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js new file mode 100644 index 000000000..313af2d71 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-browserjs-globals.js @@ -0,0 +1,83 @@ +/** + * @fileoverview Import globals from browser.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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +var fs = require("fs"); +var path = require("path"); +var helpers = require("../helpers"); +var globals = require("../globals"); + +const SCRIPTS = [ + //"browser/base/content/nsContextMenu.js", + "toolkit/content/contentAreaUtils.js", + "browser/components/places/content/editBookmarkOverlay.js", + "toolkit/components/printing/content/printUtils.js", + "toolkit/content/viewZoomOverlay.js", + "browser/components/places/content/browserPlacesViews.js", + "browser/base/content/browser.js", + "browser/components/downloads/content/downloads.js", + "browser/components/downloads/content/indicator.js", + "browser/components/customizableui/content/panelUI.js", + "toolkit/components/viewsource/content/viewSourceUtils.js", + "browser/base/content/browser-addons.js", + "browser/base/content/browser-ctrlTab.js", + "browser/base/content/browser-customization.js", + "browser/base/content/browser-devedition.js", + "browser/base/content/browser-feeds.js", + "browser/base/content/browser-fullScreenAndPointerLock.js", + "browser/base/content/browser-fullZoom.js", + "browser/base/content/browser-gestureSupport.js", + "browser/base/content/browser-media.js", + "browser/base/content/browser-places.js", + "browser/base/content/browser-plugins.js", + "browser/base/content/browser-refreshblocker.js", + "browser/base/content/browser-safebrowsing.js", + "browser/base/content/browser-sidebar.js", + "browser/base/content/browser-social.js", + "browser/base/content/browser-syncui.js", + "browser/base/content/browser-tabsintitlebar.js", + "browser/base/content/browser-thumbnails.js", + "browser/base/content/browser-trackingprotection.js", + "browser/base/content/browser-data-submission-info-bar.js", + "browser/base/content/browser-fxaccounts.js" +]; + +module.exports = function(context) { + return { + Program: function(node) { + if (helpers.getTestType(this) != "browser" && + !helpers.getIsHeadFile(this)) { + return; + } + + let filepath = helpers.getAbsoluteFilePath(context); + let root = helpers.getRootDir(filepath); + for (let script of SCRIPTS) { + let fileName = path.join(root, script); + try { + let newGlobals = globals.getGlobalsForFile(fileName); + helpers.addGlobals(newGlobals, context.getScope()); + } catch (e) { + context.report( + node, + "Could not load globals from file {{filePath}}: {{error}}", + { + filePath: path.relative(root, fileName), + error: e + } + ); + } + } + } + }; +}; 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 000000000..053a9e702 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/import-globals.js @@ -0,0 +1,15 @@ +/** + * @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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +module.exports = 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 000000000..783642f58 --- /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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +var fs = require("fs"); +var path = require("path"); +var helpers = require("../helpers"); +var globals = require("../globals"); + +module.exports = function(context) { + + function importHead(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()); + } + + // --------------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------------- + + return { + Program: function(node) { + let heads = helpers.getTestHeadFiles(this); + for (let head of heads) { + importHead(head, node); + } + } + }; +}; 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 000000000..b2e8ec294 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/mark-test-function-used.js @@ -0,0 +1,37 @@ +/** + * @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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +var helpers = require("../helpers"); + +module.exports = function(context) { + // --------------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------------- + + return { + Program: function() { + if (helpers.getTestType(this) == "browser") { + context.markVariableAsUsed("test"); + return; + } + + if (helpers.getTestType(this) == "xpcshell") { + context.markVariableAsUsed("run_test"); + return; + } + } + }; +}; 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 000000000..267f6836f --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-aArgs.js @@ -0,0 +1,55 @@ +/** + * @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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +module.exports = function(context) { + // --------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------- + + 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); + } + + 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); + } + } + } + + // --------------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------------- + + return { + "FunctionDeclaration": checkFunction, + "ArrowFunctionExpression": checkFunction, + "FunctionExpression": checkFunction + }; +}; diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js new file mode 100644 index 000000000..415cb2fc9 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-cpows-in-tests.js @@ -0,0 +1,112 @@ +/** + * @fileoverview Prevent access to CPOWs in browser mochitests. + * + * 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 helpers = require("../helpers"); + +var cpows = [ + /^gBrowser\.contentWindow/, + /^gBrowser\.contentDocument/, + /^gBrowser\.selectedBrowser.contentWindow/, + /^browser\.contentDocument/, + /^window\.content/ +]; + +var isInContentTask = false; + +module.exports = function(context) { + // --------------------------------------------------------------------------- + // Helpers + // --------------------------------------------------------------------------- + + function showError(node, identifier) { + if (isInContentTask) { + return; + } + + context.report({ + node: node, + message: identifier + + " is a possible Cross Process Object Wrapper (CPOW)." + }); + } + + function isContentTask(node) { + return node && + node.type === "MemberExpression" && + node.property.type === "Identifier" && + node.property.name === "spawn" && + node.object.type === "Identifier" && + node.object.name === "ContentTask"; + } + + // --------------------------------------------------------------------------- + // Public + // --------------------------------------------------------------------------- + + return { + CallExpression: function(node) { + if (isContentTask(node.callee)) { + isInContentTask = true; + } + }, + + "CallExpression:exit": function(node) { + if (isContentTask(node.callee)) { + isInContentTask = false; + } + }, + + MemberExpression: function(node) { + if (helpers.getTestType(this) != "browser") { + return; + } + + var expression = context.getSource(node); + + // Only report a single CPOW error per node -- so if checking + // |cpows| reports one, don't report another below. + var someCpowFound = cpows.some(function(cpow) { + if (cpow.test(expression)) { + showError(node, expression); + return true; + } + return false; + }); + if (!someCpowFound && helpers.getIsGlobalScope(context.getAncestors())) { + if (/^content\./.test(expression)) { + showError(node, expression); + return; + } + } + }, + + Identifier: function(node) { + if (helpers.getTestType(this) != "browser") { + return; + } + + var expression = context.getSource(node); + if (expression == "content" || /^content\./.test(expression)) { + if (node.parent.type === "MemberExpression" && + node.parent.object && + node.parent.object.type === "Identifier" && + node.parent.object.name != "content") { + return; + } + showError(node, expression); + return; + } + } + }; +}; diff --git a/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js new file mode 100644 index 000000000..b295f3555 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/no-single-arg-cu-import.js @@ -0,0 +1,39 @@ +/** + * @fileoverview Reject use of single argument Cu.import + * + * 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 helpers = require("../helpers"); + +module.exports = function(context) { + + // --------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + "CallExpression": function(node) { + if (node.callee.type === "MemberExpression") { + let memexp = node.callee; + if (memexp.object.type === "Identifier" && + // Only Cu, not Components.utils; see bug 1230369. + memexp.object.name === "Cu" && + memexp.property.type === "Identifier" && + memexp.property.name === "import" && + node.arguments.length === 1) { + context.report(node, "Single argument Cu.import exposes new " + + "globals to all modules"); + } + } + } + }; +}; 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 000000000..0661c91d4 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-importGlobalProperties.js @@ -0,0 +1,37 @@ +/** + * @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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +var helpers = require("../helpers"); + +module.exports = function(context) { + + // --------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + "CallExpression": function(node) { + if (node.callee.type === "MemberExpression") { + let memexp = node.callee; + if (memexp.object.type === "Identifier" && + // Only Cu, not Components.utils; see bug 1230369. + memexp.object.name === "Cu" && + memexp.property.type === "Identifier" && + memexp.property.name === "importGlobalProperties") { + context.report(node, "Unexpected call to Cu.importGlobalProperties"); + } + } + } + }; +}; 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 000000000..746f98a1f --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/reject-some-requires.js @@ -0,0 +1,48 @@ +/** + * @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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +module.exports = function(context) { + + // --------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + if (typeof(context.options[0]) !== "string") { + throw new Error("reject-some-requires expects a regexp"); + } + const RX = new RegExp(context.options[0]); + + const checkPath = function(node, path) { + if (RX.test(path)) { + context.report(node, `require(${path}) is not allowed`); + } + }; + + return { + "CallExpression": function(node) { + if (node.callee.type == "Identifier" && + node.callee.name == "require" && + node.arguments.length == 1 && + node.arguments[0].type == "Literal") { + checkPath(node, 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") { + checkPath(node, node.arguments[2].value); + } + } + }; +}; 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 000000000..a1e14e166 --- /dev/null +++ b/tools/lint/eslint/eslint-plugin-mozilla/lib/rules/var-only-at-top-level.js @@ -0,0 +1,34 @@ +/** + * @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"; + +// ----------------------------------------------------------------------------- +// Rule Definition +// ----------------------------------------------------------------------------- + +var helpers = require("../helpers"); + +module.exports = function(context) { + // --------------------------------------------------------------------------- + // Public + // -------------------------------------------------------------------------- + + return { + "VariableDeclaration": function(node) { + if (node.kind === "var") { + if (helpers.getIsGlobalScope(context.getAncestors())) { + return; + } + + context.report(node, "Unexpected var, use let or const instead."); + } + } + }; +}; -- cgit v1.2.3