tools: update eslint to 0.24.0
PR-URL: https://github.com/nodejs/io.js/pull/2072 Reviewed-By: Yosuke Furukawa <yosuke.furukawa@gmail.com> Reviewed-by: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Alex Kocharin <alex@kocharin.ru>
This commit is contained in:
parent
1a51f0058c
commit
d91e10b3bd
@ -3,6 +3,7 @@
|
||||
[![Test coverage][coveralls-image]][coveralls-url]
|
||||
[![Downloads][downloads-image]][downloads-url]
|
||||
[](https://www.bountysource.com/trackers/282608-eslint?utm_source=282608&utm_medium=shield&utm_campaign=TRACKER_BADGE)
|
||||
[](https://gitter.im/eslint/eslint?utm_source=badge&utm_medium=badge&utm_campaign=pr-badge&utm_content=badge)
|
||||
|
||||
# ESLint
|
||||
|
||||
@ -22,8 +23,26 @@ You can install ESLint using npm:
|
||||
|
||||
## Usage
|
||||
|
||||
If it's your first time using ESLint, you should set up a config file using `--init`:
|
||||
|
||||
eslint --init
|
||||
|
||||
After that, you can run ESLint on any JavaScript file:
|
||||
|
||||
eslint test.js test2.js
|
||||
|
||||
## Team
|
||||
|
||||
These folks keep the project moving and are resources for help:
|
||||
|
||||
* Nicholas C. Zakas ([@nzakas](https://github.com/nzakas)) - project lead
|
||||
* Ilya Volodin ([@ilyavolodin](https://github.com/ilyavolodin)) - reviewer
|
||||
* Brandon Mills ([@btmills](https://github.com/btmills)) - reviewer
|
||||
* Mathias Schreck ([@lo1tuma](https://github.com/lo1tuma)) - committer
|
||||
* Gyandeep Singh ([@gyandeeps](https://github.com/gyandeeps)) - committer
|
||||
* Jamund Ferguson ([@xjamundx](https://github.com/xjamundx)) - committer
|
||||
|
||||
|
||||
## Frequently Asked Questions
|
||||
|
||||
### Why don't you like JSHint???
|
||||
@ -34,12 +53,16 @@ I do like JSHint. And I like Anton and Rick. Neither of those were deciding fact
|
||||
|
||||
That's not really a question, but I got it. I'm not trying to convince you that ESLint is better than JSHint. The only thing I know is that ESLint is better than JSHint for what I'm doing. In the off chance you're doing something similar, it might be better for you. Otherwise, keep using JSHint, I'm certainly not going to tell you to stop using it.
|
||||
|
||||
### How does ESLint performance compare to JSHint?
|
||||
### How does ESLint performance compare to JSHint and JSCS?
|
||||
|
||||
ESLint is slower than JSHint, usually 2-3x slower on a single file. This is because ESLint uses Espree to construct an AST before it can evaluate your code whereas JSHint evaluates your code as it's being parsed. The speed is also based on the number of rules you enable; the more rules you enable, the slower the process.
|
||||
|
||||
Despite being slower, we believe that ESLint is fast enough to replace JSHint without causing significant pain.
|
||||
|
||||
ESLint is faster than JSCS, as ESLint uses a single-pass traversal for analysis whereas JSCS using a querying model.
|
||||
|
||||
If you are using both JSHint and JSCS on your files, then using just ESLint will be faster.
|
||||
|
||||
### Is ESLint just linting or does it also check style?
|
||||
|
||||
ESLint does both traditional linting (looking for problematic patterns) and style checking (enforcement of conventions). You can use it for both.
|
||||
@ -50,6 +73,7 @@ The following projects are using ESLint to validate their JavaScript:
|
||||
|
||||
* [Drupal](https://www.drupal.org/node/2274223)
|
||||
* [Esprima](https://github.com/ariya/esprima)
|
||||
* [io.js](https://github.com/iojs/io.js/commit/f9dd34d301ab385ae316769b85ef916f9b70b6f6)
|
||||
* [WebKit](https://bugs.webkit.org/show_bug.cgi?id=125048)
|
||||
|
||||
In addition, the following companies are using ESLint internally to validate their JavaScript:
|
||||
@ -62,11 +86,19 @@ In addition, the following companies are using ESLint internally to validate the
|
||||
|
||||
### What about ECMAScript 6 support?
|
||||
|
||||
We are implementing ECMAScript 6 support piece-by-piece starting with version 0.12.0. You'll be able to opt-in to any ECMAScript 6 feature you want to use.
|
||||
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 support through [configuration](http://eslint.org/docs/user-guide/configuring).
|
||||
|
||||
### Does ESLint support JSX?
|
||||
|
||||
Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](http://eslint.org/docs/user-guide/configuring).). Please note that supporting JSX syntax *is not* the same as supporting React. React applies specific semantics to JSX syntax that ESLint doesn't recognize. We recommend using [eslint-plugin-react](https://www.npmjs.com/package/eslint-plugin-react) if you are using React and want React semantics.
|
||||
|
||||
### What about ECMAScript 7/2016 and experimental features?
|
||||
|
||||
ESLint doesn't natively support experimental ECMAScript language features. You can use [babel-eslint](https://github.com/babel/babel-eslint) to use any option available in Babel.
|
||||
|
||||
### Where to ask for help?
|
||||
|
||||
Join our [Mailing List](https://groups.google.com/group/eslint)
|
||||
Join our [Mailing List](https://groups.google.com/group/eslint) or [Chatroom](https://gitter.im/eslint/eslint)
|
||||
|
||||
|
||||
[npm-image]: https://img.shields.io/npm/v/eslint.svg?style=flat-square
|
||||
|
@ -1,9 +1,11 @@
|
||||
#!/usr/bin/env node
|
||||
var concat = require("concat-stream"),
|
||||
configInit = require("../lib/config-initializer"),
|
||||
cli = require("../lib/cli");
|
||||
|
||||
var exitCode = 0,
|
||||
useStdIn = (process.argv.indexOf("--stdin") > -1);
|
||||
useStdIn = (process.argv.indexOf("--stdin") > -1),
|
||||
init = (process.argv.indexOf("--init") > -1);
|
||||
|
||||
if (useStdIn) {
|
||||
process.stdin.pipe(concat({ encoding: "string" }, function(text) {
|
||||
@ -15,6 +17,17 @@ if (useStdIn) {
|
||||
exitCode = 1;
|
||||
}
|
||||
}));
|
||||
} else if (init) {
|
||||
configInit.initializeConfig(function(err) {
|
||||
if (err) {
|
||||
exitCode = 1;
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
} else {
|
||||
console.log("Successfully created .eslintrc file in " + process.cwd());
|
||||
exitCode = 0;
|
||||
}
|
||||
});
|
||||
} else {
|
||||
exitCode = cli.execute(process.argv);
|
||||
}
|
||||
|
@ -28,6 +28,9 @@ module.exports = {
|
||||
"handle-callback-err": [2, "err"]
|
||||
}
|
||||
},
|
||||
worker: {
|
||||
globals: globals.worker
|
||||
},
|
||||
amd: {
|
||||
globals: globals.amd
|
||||
},
|
||||
@ -52,6 +55,12 @@ module.exports = {
|
||||
meteor: {
|
||||
globals: globals.meteor
|
||||
},
|
||||
mongo: {
|
||||
globals: globals.mongo
|
||||
},
|
||||
applescript: {
|
||||
globals: globals.applescript
|
||||
},
|
||||
es6: {
|
||||
ecmaFeatures: {
|
||||
arrowFunctions: true,
|
||||
|
@ -29,7 +29,8 @@
|
||||
"no-duplicate-case": 2,
|
||||
"no-else-return": 0,
|
||||
"no-empty": 2,
|
||||
"no-empty-class": 2,
|
||||
"no-empty-class": 0,
|
||||
"no-empty-character-class": 2,
|
||||
"no-empty-label": 2,
|
||||
"no-eq-null": 0,
|
||||
"no-eval": 2,
|
||||
@ -56,6 +57,7 @@
|
||||
"no-loop-func": 2,
|
||||
"no-mixed-requires": [0, false],
|
||||
"no-mixed-spaces-and-tabs": [2, false],
|
||||
"linebreak-style": [0, "unix"],
|
||||
"no-multi-spaces": 2,
|
||||
"no-multi-str": 2,
|
||||
"no-multiple-empty-lines": [0, {"max": 2}],
|
||||
@ -92,21 +94,27 @@
|
||||
"no-sync": 0,
|
||||
"no-ternary": 0,
|
||||
"no-trailing-spaces": 2,
|
||||
"no-this-before-super": 0,
|
||||
"no-throw-literal": 0,
|
||||
"no-undef": 2,
|
||||
"no-undef-init": 2,
|
||||
"no-undefined": 0,
|
||||
"no-unexpected-multiline": 0,
|
||||
"no-underscore-dangle": 2,
|
||||
"no-unneeded-ternary": 0,
|
||||
"no-unreachable": 2,
|
||||
"no-unused-expressions": 2,
|
||||
"no-unused-vars": [2, {"vars": "all", "args": "after-used"}],
|
||||
"no-use-before-define": 2,
|
||||
"no-void": 0,
|
||||
"no-var": 0,
|
||||
"prefer-const": 0,
|
||||
"no-warning-comments": [0, { "terms": ["todo", "fixme", "xxx"], "location": "start" }],
|
||||
"no-with": 2,
|
||||
"no-wrap-func": 2,
|
||||
|
||||
"array-bracket-spacing": [0, "never"],
|
||||
"accessor-pairs": 0,
|
||||
"block-scoped-var": 0,
|
||||
"brace-style": [0, "1tbs"],
|
||||
"camelcase": 2,
|
||||
@ -114,10 +122,13 @@
|
||||
"comma-spacing": 2,
|
||||
"comma-style": 0,
|
||||
"complexity": [0, 11],
|
||||
"computed-property-spacing": [0, "never"],
|
||||
"consistent-return": 2,
|
||||
"consistent-this": [0, "that"],
|
||||
"constructor-super": 0,
|
||||
"curly": [2, "all"],
|
||||
"default-case": 0,
|
||||
"dot-location": 0,
|
||||
"dot-notation": [2, { "allowKeywords": true }],
|
||||
"eol-last": 2,
|
||||
"eqeqeq": 2,
|
||||
@ -130,6 +141,7 @@
|
||||
"handle-callback-err": 0,
|
||||
"indent": 0,
|
||||
"key-spacing": [2, { "beforeColon": false, "afterColon": true }],
|
||||
"lines-around-comment": 0,
|
||||
"max-depth": [0, 4],
|
||||
"max-len": [0, 80, 4],
|
||||
"max-nested-callbacks": [0, 2],
|
||||
@ -138,6 +150,7 @@
|
||||
"new-cap": 2,
|
||||
"new-parens": 2,
|
||||
"newline-after-var": 0,
|
||||
"object-curly-spacing": [0, "never"],
|
||||
"object-shorthand": 0,
|
||||
"one-var": 0,
|
||||
"operator-assignment": [0, "always"],
|
||||
@ -159,6 +172,7 @@
|
||||
"space-infix-ops": 2,
|
||||
"space-return-throw-case": 2,
|
||||
"space-unary-ops": [2, { "words": true, "nonwords": false }],
|
||||
"spaced-comment": 0,
|
||||
"spaced-line-comment": [0, "always"],
|
||||
"strict": 2,
|
||||
"use-isnan": 2,
|
||||
|
@ -8,5 +8,6 @@
|
||||
module.exports = {
|
||||
linter: require("./eslint"),
|
||||
cli: require("./cli"),
|
||||
CLIEngine: require("./cli-engine")
|
||||
CLIEngine: require("./cli-engine"),
|
||||
validator: require("./config-validator")
|
||||
};
|
||||
|
@ -27,7 +27,8 @@ var fs = require("fs"),
|
||||
traverse = require("./util/traverse"),
|
||||
IgnoredPaths = require("./ignored-paths"),
|
||||
Config = require("./config"),
|
||||
util = require("./util");
|
||||
util = require("./util"),
|
||||
validator = require("./config-validator");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
@ -154,30 +155,33 @@ function calculateStatsPerRun(results) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an individual file using ESLint. Files used here are known to
|
||||
* exist, so no need to check that here.
|
||||
* @param {string} filename The filename of the file being checked.
|
||||
* Processes an source code using ESLint.
|
||||
* @param {string} text The source code to check.
|
||||
* @param {Object} configHelper The configuration options for ESLint.
|
||||
* @returns {Result} The results for linting on this file.
|
||||
* @param {string} filename An optional string representing the texts filename.
|
||||
* @returns {Result} The results for linting on this text.
|
||||
* @private
|
||||
*/
|
||||
function processFile(filename, configHelper) {
|
||||
function processText(text, configHelper, filename) {
|
||||
|
||||
// clear all existing settings for a new file
|
||||
eslint.reset();
|
||||
|
||||
var filePath = path.resolve(filename),
|
||||
var filePath,
|
||||
config,
|
||||
text,
|
||||
messages,
|
||||
stats,
|
||||
fileExtension = path.extname(filename),
|
||||
processor;
|
||||
|
||||
debug("Linting " + filePath);
|
||||
if (filename) {
|
||||
filePath = path.resolve(filename);
|
||||
}
|
||||
|
||||
filename = filename || "<text>";
|
||||
debug("Linting " + filename);
|
||||
config = configHelper.getConfig(filePath);
|
||||
loadPlugins(config.plugins);
|
||||
text = fs.readFileSync(path.resolve(filename), "utf8");
|
||||
|
||||
for (var plugin in loadedPlugins) {
|
||||
if (loadedPlugins[plugin].processors && Object.keys(loadedPlugins[plugin].processors).indexOf(fileExtension) >= 0) {
|
||||
@ -208,35 +212,38 @@ function processFile(filename, configHelper) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Processes an source code using ESLint.
|
||||
* @param {string} text The source code to check.
|
||||
* Processes an individual file using ESLint. Files used here are known to
|
||||
* exist, so no need to check that here.
|
||||
* @param {string} filename The filename of the file being checked.
|
||||
* @param {Object} configHelper The configuration options for ESLint.
|
||||
* @param {string} filename An optional string representing the texts filename.
|
||||
* @returns {Result} The results for linting on this text.
|
||||
* @returns {Result} The results for linting on this file.
|
||||
* @private
|
||||
*/
|
||||
function processText(text, configHelper, filename) {
|
||||
function processFile(filename, configHelper) {
|
||||
|
||||
// clear all existing settings for a new file
|
||||
eslint.reset();
|
||||
var text = fs.readFileSync(path.resolve(filename), "utf8");
|
||||
|
||||
var config,
|
||||
messages,
|
||||
stats;
|
||||
|
||||
filename = filename || "<text>";
|
||||
debug("Linting " + filename);
|
||||
config = configHelper.getConfig();
|
||||
loadPlugins(config.plugins);
|
||||
messages = eslint.verify(text, config, filename);
|
||||
|
||||
stats = calculateStatsPerFile(messages);
|
||||
return processText(text, configHelper, filename);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns result with warning by ignore settings
|
||||
* @param {string} filePath File path of checked code
|
||||
* @returns {Result} Result with single warning
|
||||
* @private
|
||||
*/
|
||||
function createIgnoreResult(filePath) {
|
||||
return {
|
||||
filePath: filename,
|
||||
messages: messages,
|
||||
errorCount: stats.errorCount,
|
||||
warningCount: stats.warningCount
|
||||
filePath: filePath,
|
||||
messages: [
|
||||
{
|
||||
fatal: false,
|
||||
severity: 1,
|
||||
message: "File ignored because of your .eslintignore file. Use --no-ignore to override."
|
||||
}
|
||||
],
|
||||
errorCount: 0,
|
||||
warningCount: 1
|
||||
};
|
||||
}
|
||||
|
||||
@ -265,13 +272,29 @@ function CLIEngine(options) {
|
||||
});
|
||||
}
|
||||
|
||||
loadPlugins(this.options.plugins);
|
||||
Object.keys(this.options.rules || {}).forEach(function(name) {
|
||||
validator.validateRuleOptions(name, this.options.rules[name], "CLI");
|
||||
}.bind(this));
|
||||
}
|
||||
|
||||
CLIEngine.prototype = {
|
||||
|
||||
constructor: CLIEngine,
|
||||
|
||||
/**
|
||||
* Add a plugin by passing it's configuration
|
||||
* @param {string} name Name of the plugin.
|
||||
* @param {Object} pluginobject Plugin configuration object.
|
||||
* @returns {void}
|
||||
*/
|
||||
addPlugin: function(name, pluginobject) {
|
||||
var pluginNameWithoutPrefix = util.removePluginPrefix(util.removeNameSpace(name));
|
||||
if (pluginobject.rules) {
|
||||
rules.import(pluginobject.rules, pluginNameWithoutPrefix);
|
||||
}
|
||||
loadedPlugins[pluginNameWithoutPrefix] = pluginobject;
|
||||
},
|
||||
|
||||
/**
|
||||
* Executes the current configuration on an array of file and directory names.
|
||||
* @param {string[]} files An array of file and directory names.
|
||||
@ -303,18 +326,7 @@ CLIEngine.prototype = {
|
||||
if (options.ignore) {
|
||||
files.forEach(function(file) {
|
||||
if (fs.statSync(path.resolve(file)).isFile() && processed.indexOf(file) === -1) {
|
||||
results.push({
|
||||
filePath: file,
|
||||
messages: [
|
||||
{
|
||||
fatal: false,
|
||||
severity: 1,
|
||||
message: "File ignored because of your .eslintignore file. Use --no-ignore to override."
|
||||
}
|
||||
],
|
||||
errorCount: 0,
|
||||
warningCount: 1
|
||||
});
|
||||
results.push(createIgnoreResult(file));
|
||||
}
|
||||
});
|
||||
}
|
||||
@ -336,11 +348,19 @@ CLIEngine.prototype = {
|
||||
*/
|
||||
executeOnText: function(text, filename) {
|
||||
|
||||
var configHelper = new Config(this.options),
|
||||
results = [],
|
||||
stats;
|
||||
var results = [],
|
||||
stats,
|
||||
options = this.options,
|
||||
configHelper = new Config(options),
|
||||
ignoredPaths = IgnoredPaths.load(options),
|
||||
exclude = ignoredPaths.contains.bind(ignoredPaths);
|
||||
|
||||
if (filename && options.ignore && exclude(filename)) {
|
||||
results.push(createIgnoreResult(filename));
|
||||
} else {
|
||||
results.push(processText(text, configHelper, filename));
|
||||
}
|
||||
|
||||
results.push(processText(text, configHelper, filename));
|
||||
stats = calculateStatsPerRun(results);
|
||||
|
||||
return {
|
||||
@ -414,8 +434,6 @@ CLIEngine.prototype = {
|
||||
} else {
|
||||
return null;
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
};
|
||||
|
@ -45,6 +45,7 @@ function translateOptions(cliOptions) {
|
||||
globals: cliOptions.global,
|
||||
ignore: cliOptions.ignore,
|
||||
ignorePath: cliOptions.ignorePath,
|
||||
ignorePattern: cliOptions.ignorePattern,
|
||||
configFile: cliOptions.config,
|
||||
rulePaths: cliOptions.rulesdir,
|
||||
reset: cliOptions.reset,
|
||||
|
145
tools/eslint/lib/config-initializer.js
Normal file
145
tools/eslint/lib/config-initializer.js
Normal file
@ -0,0 +1,145 @@
|
||||
/**
|
||||
* @fileoverview Config initialization wizard.
|
||||
* @author Ilya Volodin
|
||||
* @copyright 2015 Ilya Volodin. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var exec = require("child_process").exec,
|
||||
fs = require("fs"),
|
||||
inquirer = require("inquirer"),
|
||||
yaml = require("js-yaml");
|
||||
|
||||
/* istanbul ignore next: hard to test fs function */
|
||||
/**
|
||||
* Create .eslintrc file in the current working directory
|
||||
* @param {object} config object that contains user's answers
|
||||
* @param {bool} isJson should config file be json or yaml
|
||||
* @param {function} callback function to call once the file is written.
|
||||
* @returns {void}
|
||||
*/
|
||||
function writeFile(config, isJson, callback) {
|
||||
try {
|
||||
fs.writeFileSync("./.eslintrc", isJson ? JSON.stringify(config, null, 4) : yaml.safeDump(config));
|
||||
} catch (e) {
|
||||
callback(e);
|
||||
return;
|
||||
}
|
||||
if (config.plugins && config.plugins.indexOf("react") >= 0) {
|
||||
exec("npm i eslint-plugin-react --save-dev", callback);
|
||||
} else {
|
||||
callback();
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* process user's answers and create config object
|
||||
* @param {object} answers answers received from inquirer
|
||||
* @returns {object} config object
|
||||
*/
|
||||
function processAnswers(answers) {
|
||||
var config = {rules: {}, env: {}};
|
||||
config.rules.indent = [2, answers.indent];
|
||||
config.rules.quotes = [2, answers.quotes];
|
||||
config.rules["linebreak-style"] = [2, answers.linebreak];
|
||||
config.rules.semi = [2, answers.semi ? "always" : "never"];
|
||||
if (answers.es6) {
|
||||
config.env.es6 = true;
|
||||
}
|
||||
answers.env.forEach(function(env) {
|
||||
config.env[env] = true;
|
||||
});
|
||||
if (answers.jsx) {
|
||||
config.ecmaFeatures = {jsx: true};
|
||||
if (answers.react) {
|
||||
config.plugins = ["react"];
|
||||
}
|
||||
}
|
||||
return config;
|
||||
}
|
||||
|
||||
/* istanbul ignore next: no need to test inquirer*/
|
||||
/**
|
||||
* Ask use a few questions on command prompt
|
||||
* @param {function} callback callback function when file has been written
|
||||
* @returns {void}
|
||||
*/
|
||||
function promptUser(callback) {
|
||||
inquirer.prompt([
|
||||
{
|
||||
type: "list",
|
||||
name: "indent",
|
||||
message: "What style of indentation do you use?",
|
||||
default: "tabs",
|
||||
choices: [{name: "Tabs", value: "tab"}, {name: "Spaces", value: 4}]
|
||||
},
|
||||
{
|
||||
type: "list",
|
||||
name: "quotes",
|
||||
message: "What quotes do you use for strings?",
|
||||
default: "double",
|
||||
choices: [{name: "Double", value: "double"}, {name: "Single", value: "single"}]
|
||||
},
|
||||
{
|
||||
type: "list",
|
||||
name: "linebreak",
|
||||
message: "What line endings do you use?",
|
||||
default: "unix",
|
||||
choices: [{name: "Unix", value: "unix"}, {name: "Windows", value: "windows"}]
|
||||
},
|
||||
{
|
||||
type: "confirm",
|
||||
name: "semi",
|
||||
message: "Do you require semicolons?",
|
||||
default: true
|
||||
},
|
||||
{
|
||||
type: "confirm",
|
||||
name: "es6",
|
||||
message: "Are you using ECMAScript 6 features?",
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: "checkbox",
|
||||
name: "env",
|
||||
message: "Where will your code run?",
|
||||
default: ["browser"],
|
||||
choices: [{name: "Node", value: "node"}, {name: "Browser", value: "browser"}]
|
||||
},
|
||||
{
|
||||
type: "confirm",
|
||||
name: "jsx",
|
||||
message: "Do you use JSX?",
|
||||
default: false
|
||||
},
|
||||
{
|
||||
type: "confirm",
|
||||
name: "react",
|
||||
message: "Do you use React",
|
||||
default: false,
|
||||
when: function (answers) {
|
||||
return answers.jsx;
|
||||
}
|
||||
},
|
||||
{
|
||||
type: "list",
|
||||
name: "format",
|
||||
message: "What format do you want your config file to be in?",
|
||||
default: "JSON",
|
||||
choices: ["JSON", "YAML"]
|
||||
}
|
||||
], function(answers) {
|
||||
var config = processAnswers(answers);
|
||||
writeFile(config, answers.format === "JSON", callback);
|
||||
});
|
||||
}
|
||||
|
||||
var init = {
|
||||
processAnswers: processAnswers,
|
||||
initializeConfig: /* istanbul ignore next */ function(callback) {
|
||||
promptUser(callback);
|
||||
}
|
||||
};
|
||||
|
||||
module.exports = init;
|
110
tools/eslint/lib/config-validator.js
Normal file
110
tools/eslint/lib/config-validator.js
Normal file
@ -0,0 +1,110 @@
|
||||
/**
|
||||
* @fileoverview Validates configs.
|
||||
* @author Brandon Mills
|
||||
* @copyright 2015 Brandon Mills
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
var rules = require("./rules"),
|
||||
schemaValidator = require("is-my-json-valid");
|
||||
|
||||
var validators = {
|
||||
rules: Object.create(null)
|
||||
};
|
||||
|
||||
/**
|
||||
* Gets a complete options schema for a rule.
|
||||
* @param {string} id The rule's unique name.
|
||||
* @returns {object} JSON Schema for the rule's options.
|
||||
*/
|
||||
function getRuleOptionsSchema(id) {
|
||||
var rule = rules.get(id),
|
||||
schema = rule && rule.schema;
|
||||
|
||||
if (!schema) {
|
||||
return {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"enum": [0, 1, 2]
|
||||
}
|
||||
],
|
||||
"minItems": 1
|
||||
};
|
||||
}
|
||||
|
||||
// Given a tuple of schemas, insert warning level at the beginning
|
||||
if (Array.isArray(schema)) {
|
||||
return {
|
||||
"type": "array",
|
||||
"items": [
|
||||
{
|
||||
"enum": [0, 1, 2]
|
||||
}
|
||||
].concat(schema),
|
||||
"minItems": 1,
|
||||
"maxItems": schema.length + 1
|
||||
};
|
||||
}
|
||||
|
||||
// Given a full schema, leave it alone
|
||||
return schema;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a rule's options against its schema.
|
||||
* @param {string} id The rule's unique name.
|
||||
* @param {array|number} options The given options for the rule.
|
||||
* @param {string} source The name of the configuration source.
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateRuleOptions(id, options, source) {
|
||||
var validateRule = validators.rules[id],
|
||||
message;
|
||||
|
||||
if (!validateRule) {
|
||||
validateRule = schemaValidator(getRuleOptionsSchema(id), { verbose: true });
|
||||
validators.rules[id] = validateRule;
|
||||
}
|
||||
|
||||
if (typeof options === "number") {
|
||||
options = [options];
|
||||
}
|
||||
|
||||
validateRule(options);
|
||||
|
||||
if (validateRule.errors) {
|
||||
message = [
|
||||
source, ":\n",
|
||||
"\tConfiguration for rule \"", id, "\" is invalid:\n"
|
||||
];
|
||||
validateRule.errors.forEach(function (error) {
|
||||
message.push(
|
||||
"\tValue \"", error.value, "\" ", error.message, ".\n"
|
||||
);
|
||||
});
|
||||
|
||||
throw new Error(message.join(""));
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates an entire config object.
|
||||
* @param {object} config The config object to validate.
|
||||
* @param {string} source The location to report with any errors.
|
||||
* @returns {void}
|
||||
*/
|
||||
function validate(config, source) {
|
||||
if (typeof config.rules === "object") {
|
||||
Object.keys(config.rules).forEach(function (id) {
|
||||
validateRuleOptions(id, config.rules[id], source);
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = {
|
||||
getRuleOptionsSchema: getRuleOptionsSchema,
|
||||
validate: validate,
|
||||
validateRuleOptions: validateRuleOptions
|
||||
};
|
@ -20,7 +20,9 @@ var fs = require("fs"),
|
||||
assign = require("object-assign"),
|
||||
debug = require("debug"),
|
||||
yaml = require("js-yaml"),
|
||||
userHome = require("user-home");
|
||||
userHome = require("user-home"),
|
||||
isAbsolutePath = require("path-is-absolute"),
|
||||
validator = require("./config-validator");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants
|
||||
@ -43,24 +45,98 @@ var loadedPlugins = Object.create(null);
|
||||
|
||||
debug = debug("eslint:config");
|
||||
|
||||
/**
|
||||
* Determines if a given string represents a filepath or not using the same
|
||||
* conventions as require(), meaning that the first character must be nonalphanumeric
|
||||
* and not the @ sign which is used for scoped packages to be considered a file path.
|
||||
* @param {string} filePath The string to check.
|
||||
* @returns {boolean} True if it's a filepath, false if not.
|
||||
* @private
|
||||
*/
|
||||
function isFilePath(filePath) {
|
||||
return isAbsolutePath(filePath) || !/\w|@/.test(filePath[0]);
|
||||
}
|
||||
|
||||
/**
|
||||
* Load and parse a JSON config object from a file.
|
||||
* @param {string} filePath the path to the JSON config file
|
||||
* @returns {Object} the parsed config object (empty object if there was a parse error)
|
||||
* @private
|
||||
*/
|
||||
function loadConfig(filePath) {
|
||||
var config = {};
|
||||
|
||||
if (filePath) {
|
||||
try {
|
||||
config = yaml.safeLoad(stripComments(fs.readFileSync(filePath, "utf8"))) || {};
|
||||
} catch (e) {
|
||||
debug("Error reading YAML file: " + filePath);
|
||||
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
|
||||
throw e;
|
||||
|
||||
if (isFilePath(filePath)) {
|
||||
try {
|
||||
config = yaml.safeLoad(stripComments(fs.readFileSync(filePath, "utf8"))) || {};
|
||||
} catch (e) {
|
||||
debug("Error reading YAML file: " + filePath);
|
||||
e.message = "Cannot read config file: " + filePath + "\nError: " + e.message;
|
||||
throw e;
|
||||
}
|
||||
|
||||
if (path.basename(filePath) === PACKAGE_CONFIG_FILENAME) {
|
||||
config = config[PACKAGE_CONFIG_FIELD_NAME] || {};
|
||||
}
|
||||
|
||||
} else {
|
||||
|
||||
// it's a package
|
||||
if (filePath.indexOf("eslint-config-") === -1) {
|
||||
if (filePath.indexOf("@") === 0) {
|
||||
// for scoped packages, insert the eslint-config after the first /
|
||||
filePath = filePath.replace(/^([^\/]+\/)(.*)$/, "$1eslint-config-$2");
|
||||
} else {
|
||||
filePath = "eslint-config-" + filePath;
|
||||
}
|
||||
}
|
||||
|
||||
config = util.mergeConfigs(config, require(filePath));
|
||||
}
|
||||
|
||||
validator.validate(config, filePath);
|
||||
|
||||
// If an `extends` property is defined, it represents a configuration file to use as
|
||||
// a "parent". Load the referenced file and merge the configuration recursively.
|
||||
if (config.extends) {
|
||||
var configExtends = config.extends;
|
||||
|
||||
if (!Array.isArray(config.extends)) {
|
||||
configExtends = [config.extends];
|
||||
}
|
||||
|
||||
// Make the last element in an array take the highest precedence
|
||||
config = configExtends.reduceRight(function (previousValue, parentPath) {
|
||||
|
||||
if (isFilePath(parentPath)) {
|
||||
// If the `extends` path is relative, use the directory of the current configuration
|
||||
// file as the reference point. Otherwise, use as-is.
|
||||
parentPath = (!isAbsolutePath(parentPath) ?
|
||||
path.join(path.dirname(filePath), parentPath) :
|
||||
parentPath
|
||||
);
|
||||
}
|
||||
|
||||
try {
|
||||
return util.mergeConfigs(loadConfig(parentPath), previousValue);
|
||||
} catch (e) {
|
||||
// If the file referenced by `extends` failed to load, add the path to the
|
||||
// configuration file that referenced it to the error message so the user is
|
||||
// able to see where it was referenced from, then re-throw
|
||||
e.message += "\nReferenced from: " + filePath;
|
||||
throw e;
|
||||
}
|
||||
|
||||
}, config);
|
||||
|
||||
}
|
||||
|
||||
|
||||
}
|
||||
|
||||
|
||||
return config;
|
||||
}
|
||||
|
||||
@ -72,37 +148,35 @@ function loadConfig(filePath) {
|
||||
function getPluginsConfig(pluginNames) {
|
||||
var pluginConfig = {};
|
||||
|
||||
if (pluginNames) {
|
||||
pluginNames.forEach(function (pluginName) {
|
||||
var pluginNamespace = util.getNamespace(pluginName),
|
||||
pluginNameWithoutNamespace = util.removeNameSpace(pluginName),
|
||||
pluginNameWithoutPrefix = util.removePluginPrefix(pluginNameWithoutNamespace),
|
||||
plugin = {},
|
||||
rules = {};
|
||||
pluginNames.forEach(function (pluginName) {
|
||||
var pluginNamespace = util.getNamespace(pluginName),
|
||||
pluginNameWithoutNamespace = util.removeNameSpace(pluginName),
|
||||
pluginNameWithoutPrefix = util.removePluginPrefix(pluginNameWithoutNamespace),
|
||||
plugin = {},
|
||||
rules = {};
|
||||
|
||||
if (!loadedPlugins[pluginNameWithoutPrefix]) {
|
||||
try {
|
||||
plugin = require(pluginNamespace + util.PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix);
|
||||
loadedPlugins[pluginNameWithoutPrefix] = plugin;
|
||||
} catch(err) {
|
||||
debug("Failed to load plugin configuration for " + pluginNameWithoutPrefix + ". Proceeding without it.");
|
||||
plugin = { rulesConfig: {}};
|
||||
}
|
||||
} else {
|
||||
plugin = loadedPlugins[pluginNameWithoutPrefix];
|
||||
if (!loadedPlugins[pluginNameWithoutPrefix]) {
|
||||
try {
|
||||
plugin = require(pluginNamespace + util.PLUGIN_NAME_PREFIX + pluginNameWithoutPrefix);
|
||||
loadedPlugins[pluginNameWithoutPrefix] = plugin;
|
||||
} catch(err) {
|
||||
debug("Failed to load plugin configuration for " + pluginNameWithoutPrefix + ". Proceeding without it.");
|
||||
plugin = { rulesConfig: {}};
|
||||
}
|
||||
} else {
|
||||
plugin = loadedPlugins[pluginNameWithoutPrefix];
|
||||
}
|
||||
|
||||
if (!plugin.rulesConfig) {
|
||||
plugin.rulesConfig = {};
|
||||
}
|
||||
if (!plugin.rulesConfig) {
|
||||
plugin.rulesConfig = {};
|
||||
}
|
||||
|
||||
Object.keys(plugin.rulesConfig).forEach(function(item) {
|
||||
rules[pluginNameWithoutPrefix + "/" + item] = plugin.rulesConfig[item];
|
||||
});
|
||||
|
||||
pluginConfig = util.mergeConfigs(pluginConfig, rules);
|
||||
Object.keys(plugin.rulesConfig).forEach(function(item) {
|
||||
rules[pluginNameWithoutPrefix + "/" + item] = plugin.rulesConfig[item];
|
||||
});
|
||||
}
|
||||
|
||||
pluginConfig = util.mergeConfigs(pluginConfig, rules);
|
||||
});
|
||||
|
||||
return {rules: pluginConfig};
|
||||
}
|
||||
@ -147,15 +221,12 @@ function getLocalConfig(thisConfig, directory) {
|
||||
continue;
|
||||
}
|
||||
|
||||
debug("Loading " + localConfigFile);
|
||||
localConfig = loadConfig(localConfigFile);
|
||||
|
||||
if (path.basename(localConfigFile) !== LOCAL_CONFIG_FILENAME) {
|
||||
|
||||
// Don't consider a local config file found if the package.json doesn't have the eslintConfig field.
|
||||
if (!localConfig.hasOwnProperty(PACKAGE_CONFIG_FIELD_NAME)) {
|
||||
continue;
|
||||
}
|
||||
localConfig = localConfig[PACKAGE_CONFIG_FIELD_NAME] || {};
|
||||
// Don't consider a local config file found if the config is empty.
|
||||
if (!Object.keys(localConfig).length) {
|
||||
continue;
|
||||
}
|
||||
|
||||
found = true;
|
||||
@ -242,7 +313,6 @@ function Config(options) {
|
||||
require(path.resolve(__dirname, "..", "conf", "eslint.json"));
|
||||
}
|
||||
|
||||
this.baseConfig.format = options.format;
|
||||
this.useEslintrc = (options.useEslintrc !== false);
|
||||
|
||||
this.env = (options.envs || []).reduce(function (envs, name) {
|
||||
@ -317,10 +387,8 @@ Config.prototype.getConfig = function (filePath) {
|
||||
}
|
||||
|
||||
// Step 6: Merge in command line environments
|
||||
if (this.env) {
|
||||
debug("Merging command line environment settings");
|
||||
config = util.mergeConfigs(config, createEnvironmentConfig(this.env, this.options.reset));
|
||||
}
|
||||
debug("Merging command line environment settings");
|
||||
config = util.mergeConfigs(config, createEnvironmentConfig(this.env, this.options.reset));
|
||||
|
||||
// Step 7: Merge in command line rules
|
||||
if (this.options.rules) {
|
||||
|
@ -18,16 +18,13 @@ var estraverse = require("estraverse-fb"),
|
||||
timing = require("./timing"),
|
||||
createTokenStore = require("./token-store.js"),
|
||||
EventEmitter = require("events").EventEmitter,
|
||||
escapeRegExp = require("escape-string-regexp");
|
||||
escapeRegExp = require("escape-string-regexp"),
|
||||
validator = require("./config-validator");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
// TODO: Remove when estraverse is updated
|
||||
estraverse.Syntax.Super = "Super";
|
||||
estraverse.VisitorKeys.Super = [];
|
||||
|
||||
/**
|
||||
* Parses a list of "name:boolean_value" or/and "name" options divided by comma or
|
||||
* whitespace.
|
||||
@ -238,13 +235,14 @@ function enableReporting(reportingConfig, start, rulesToEnable) {
|
||||
* Parses comments in file to extract file-specific config of rules, globals
|
||||
* and environments and merges them with global config; also code blocks
|
||||
* where reporting is disabled or enabled and merges them with reporting config.
|
||||
* @param {string} filename The file being checked.
|
||||
* @param {ASTNode} ast The top node of the AST.
|
||||
* @param {Object} config The existing configuration data.
|
||||
* @param {Object[]} reportingConfig The existing reporting configuration data.
|
||||
* @param {Object[]} messages The messages queue.
|
||||
* @returns {void}
|
||||
*/
|
||||
function modifyConfigsFromComments(ast, config, reportingConfig, messages) {
|
||||
function modifyConfigsFromComments(filename, ast, config, reportingConfig, messages) {
|
||||
|
||||
var commentConfig = {
|
||||
astGlobals: {},
|
||||
@ -284,9 +282,8 @@ function modifyConfigsFromComments(ast, config, reportingConfig, messages) {
|
||||
var items = parseJsonConfig(value, comment.loc, messages);
|
||||
Object.keys(items).forEach(function(name) {
|
||||
var ruleValue = items[name];
|
||||
if (typeof ruleValue === "number" || (Array.isArray(ruleValue) && typeof ruleValue[0] === "number")) {
|
||||
commentRules[name] = ruleValue;
|
||||
}
|
||||
validator.validateRuleOptions(name, ruleValue, filename + " line " + comment.loc.start.line);
|
||||
commentRules[name] = ruleValue;
|
||||
});
|
||||
break;
|
||||
|
||||
@ -302,11 +299,10 @@ function modifyConfigsFromComments(ast, config, reportingConfig, messages) {
|
||||
}
|
||||
});
|
||||
|
||||
// apply environment rules before user rules
|
||||
// apply environment configs
|
||||
Object.keys(commentConfig.env).forEach(function (name) {
|
||||
var environmentRules = environments[name] && environments[name].rules;
|
||||
if (commentConfig.env[name] && environmentRules) {
|
||||
assign(commentConfig.rules, environmentRules);
|
||||
if (environments[name]) {
|
||||
util.mergeConfigs(commentConfig, environments[name]);
|
||||
}
|
||||
});
|
||||
assign(commentConfig.rules, commentRules);
|
||||
@ -461,12 +457,14 @@ module.exports = (function() {
|
||||
});
|
||||
} catch (ex) {
|
||||
|
||||
// If the message includes a leading line number, strip it:
|
||||
var message = ex.message.replace(/^line \d+:/i, "").trim();
|
||||
|
||||
messages.push({
|
||||
fatal: true,
|
||||
severity: 2,
|
||||
|
||||
// messages come as "Line X: Unexpected token foo", so strip off leading part
|
||||
message: ex.message.substring(ex.message.indexOf(":") + 1).trim(),
|
||||
message: message,
|
||||
|
||||
line: ex.lineNumber,
|
||||
column: ex.column
|
||||
@ -613,7 +611,7 @@ module.exports = (function() {
|
||||
currentAST = ast;
|
||||
|
||||
// parse global comments and modify config
|
||||
modifyConfigsFromComments(ast, config, reportingConfig, messages);
|
||||
modifyConfigsFromComments(filename, ast, config, reportingConfig, messages);
|
||||
|
||||
// enable appropriate rules
|
||||
Object.keys(config.rules).filter(function(key) {
|
||||
@ -774,7 +772,7 @@ module.exports = (function() {
|
||||
}
|
||||
|
||||
Object.keys(opts || {}).forEach(function (key) {
|
||||
var rx = new RegExp("{{" + escapeRegExp(key) + "}}", "g");
|
||||
var rx = new RegExp(escapeRegExp("{{" + key + "}}"), "g");
|
||||
message = message.replace(rx, opts[key]);
|
||||
});
|
||||
|
||||
@ -888,9 +886,24 @@ module.exports = (function() {
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Check to see if its a ES6 export declaration
|
||||
* @param {ASTNode} astNode - any node
|
||||
* @returns {boolean} whether the given node represents a export declaration
|
||||
*/
|
||||
function looksLikeExport(astNode) {
|
||||
return astNode.type === "ExportDefaultDeclaration" || astNode.type === "ExportNamedDeclaration" ||
|
||||
astNode.type === "ExportAllDeclaration" || astNode.type === "ExportSpecifier";
|
||||
}
|
||||
|
||||
switch (node.type) {
|
||||
case "FunctionDeclaration":
|
||||
return findJSDocComment(node.leadingComments);
|
||||
if (looksLikeExport(parent)) {
|
||||
return findJSDocComment(parent.leadingComments);
|
||||
} else {
|
||||
return findJSDocComment(node.leadingComments);
|
||||
}
|
||||
break;
|
||||
|
||||
case "ArrowFunctionExpression":
|
||||
case "FunctionExpression":
|
||||
@ -958,7 +971,7 @@ module.exports = (function() {
|
||||
// if current node is function declaration, add it to the list
|
||||
var current = controller.current();
|
||||
if (["FunctionDeclaration", "FunctionExpression",
|
||||
"ArrowFunctionExpression"].indexOf(current.type) >= 0) {
|
||||
"ArrowFunctionExpression", "SwitchStatement"].indexOf(current.type) >= 0) {
|
||||
parents.push(current);
|
||||
}
|
||||
|
||||
|
@ -78,7 +78,7 @@ FileFinder.prototype.findInDirectoryOrParents = function (directory) {
|
||||
while (directory !== child) {
|
||||
dirs[searched++] = directory;
|
||||
|
||||
if (getDirectoryEntries(directory).indexOf(name) !== -1) {
|
||||
if (getDirectoryEntries(directory).indexOf(name) !== -1 && fs.statSync(path.resolve(directory, name)).isFile()) {
|
||||
filePath = path.resolve(directory, name);
|
||||
break;
|
||||
}
|
||||
@ -137,7 +137,7 @@ FileFinder.prototype.findAllInDirectoryAndParents = function (directory) {
|
||||
for (i = 0; i < fileNamesCount; i++) {
|
||||
name = fileNames[i];
|
||||
|
||||
if (getDirectoryEntries(directory).indexOf(name) !== -1) {
|
||||
if (getDirectoryEntries(directory).indexOf(name) !== -1 && fs.statSync(path.resolve(directory, name)).isFile()) {
|
||||
filePath = path.resolve(directory, name);
|
||||
|
||||
// Add the file path to the cache of each directory searched.
|
||||
|
@ -66,11 +66,7 @@ module.exports = function(results) {
|
||||
// The first error will be logged as message key
|
||||
// This is to adhere to TAP 13 loosely defined specification of having a message key
|
||||
if ("message" in diagnostics) {
|
||||
if ("messages" in diagnostics) {
|
||||
diagnostics.messages.push(diagnostic);
|
||||
} else {
|
||||
diagnostics.messages = [diagnostic];
|
||||
}
|
||||
diagnostics.messages = [diagnostic];
|
||||
} else {
|
||||
diagnostics = diagnostic;
|
||||
}
|
||||
|
@ -96,6 +96,10 @@ IgnoredPaths.load = function (options) {
|
||||
patterns = [];
|
||||
}
|
||||
|
||||
if (options.ignorePattern) {
|
||||
patterns.push(options.ignorePattern);
|
||||
}
|
||||
|
||||
return new IgnoredPaths(patterns);
|
||||
};
|
||||
|
||||
|
@ -89,6 +89,11 @@ module.exports = optionator({
|
||||
default: "true",
|
||||
description: "Disable use of .eslintignore"
|
||||
},
|
||||
{
|
||||
option: "ignore-pattern",
|
||||
type: "String",
|
||||
description: "Pattern of files to ignore (in addition to those in .eslintignore)"
|
||||
},
|
||||
{
|
||||
option: "color",
|
||||
type: "Boolean",
|
||||
@ -117,5 +122,11 @@ module.exports = optionator({
|
||||
option: "stdin-filename",
|
||||
type: "String",
|
||||
description: "Specify filename to process STDIN as"
|
||||
},
|
||||
{
|
||||
option: "init",
|
||||
type: "Boolean",
|
||||
default: "false",
|
||||
description: "Run config initialization wizard"
|
||||
}]
|
||||
});
|
||||
|
65
tools/eslint/lib/rules/accessor-pairs.js
Normal file
65
tools/eslint/lib/rules/accessor-pairs.js
Normal file
@ -0,0 +1,65 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag wrapping non-iife in parens
|
||||
* @author Gyandeep Singh
|
||||
* @copyright 2015 Gyandeep Singh. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var config = context.options[0] || {};
|
||||
var checkGetWithoutSet = config.getWithoutSet === true;
|
||||
var checkSetWithoutGet = config.setWithoutGet !== false;
|
||||
|
||||
/**
|
||||
* Checks a object expression to see if it has setter and getter both present or none.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkLonelySetGet(node) {
|
||||
var isSetPresent = false;
|
||||
var isGetPresent = false;
|
||||
var propLength = node.properties.length;
|
||||
|
||||
for (var i = 0; i < propLength; i++) {
|
||||
var propToCheck = node.properties[i].kind === "init" ? node.properties[i].key.name : node.properties[i].kind;
|
||||
|
||||
switch (propToCheck) {
|
||||
case "set":
|
||||
isSetPresent = true;
|
||||
break;
|
||||
|
||||
case "get":
|
||||
isGetPresent = true;
|
||||
break;
|
||||
|
||||
default:
|
||||
// Do nothing
|
||||
}
|
||||
|
||||
if (isSetPresent && isGetPresent) {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
if (checkSetWithoutGet && isSetPresent && !isGetPresent) {
|
||||
context.report(node, "Getter is not present");
|
||||
} else if (checkGetWithoutSet && isGetPresent && !isSetPresent) {
|
||||
context.report(node, "Setter is not present");
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"ObjectExpression": function (node) {
|
||||
if (checkSetWithoutGet || checkGetWithoutSet) {
|
||||
checkLonelySetGet(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
180
tools/eslint/lib/rules/array-bracket-spacing.js
Normal file
180
tools/eslint/lib/rules/array-bracket-spacing.js
Normal file
@ -0,0 +1,180 @@
|
||||
/**
|
||||
* @fileoverview Disallows or enforces spaces inside of array brackets.
|
||||
* @author Jamund Ferguson
|
||||
* @copyright 2015 Jamund Ferguson. All rights reserved.
|
||||
* @copyright 2014 Brandyn Bennett. All rights reserved.
|
||||
* @copyright 2014 Michael Ficarra. No rights reserved.
|
||||
* @copyright 2014 Vignesh Anand. All rights reserved.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var spaced = context.options[0] === "always";
|
||||
|
||||
/**
|
||||
* Determines whether an option is set, relative to the spacing option.
|
||||
* If spaced is "always", then check whether option is set to false.
|
||||
* If spaced is "never", then check whether option is set to true.
|
||||
* @param {Object} option - The option to exclude.
|
||||
* @returns {boolean} Whether or not the property is excluded.
|
||||
*/
|
||||
function isOptionSet(option) {
|
||||
return context.options[1] != null ? context.options[1][option] === !spaced : false;
|
||||
}
|
||||
|
||||
var options = {
|
||||
spaced: spaced,
|
||||
singleElementException: isOptionSet("singleValue"),
|
||||
objectsInArraysException: isOptionSet("objectsInArrays"),
|
||||
arraysInArraysException: isOptionSet("arraysInArrays")
|
||||
};
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines whether two adjacent tokens are have whitespace between them.
|
||||
* @param {Object} left - The left token object.
|
||||
* @param {Object} right - The right token object.
|
||||
* @returns {boolean} Whether or not there is space between the tokens.
|
||||
*/
|
||||
function isSpaced(left, right) {
|
||||
return left.range[1] < right.range[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two adjacent tokens are on the same line.
|
||||
* @param {Object} left - The left token object.
|
||||
* @param {Object} right - The right token object.
|
||||
* @returns {boolean} Whether or not the tokens are on the same line.
|
||||
*/
|
||||
function isSameLine(left, right) {
|
||||
return left.loc.start.line === right.loc.start.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space after the first token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoBeginningSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"There should be no space after '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space before the last token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoEndingSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"There should be no space before '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space after the first token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredBeginningSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"A space is required after '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space before the last token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredEndingSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"A space is required before '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the spacing around array brackets
|
||||
* @param {ASTNode} node - The node we're checking for spacing
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateArraySpacing(node) {
|
||||
if (node.elements.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var first = context.getFirstToken(node),
|
||||
second = context.getFirstToken(node, 1),
|
||||
penultimate = context.getLastToken(node, 1),
|
||||
last = context.getLastToken(node);
|
||||
|
||||
var openingBracketMustBeSpaced =
|
||||
options.objectsInArraysException && second.value === "{" ||
|
||||
options.arraysInArraysException && second.value === "[" ||
|
||||
options.singleElementException && node.elements.length === 1
|
||||
? !options.spaced : options.spaced;
|
||||
|
||||
var closingBracketMustBeSpaced =
|
||||
options.objectsInArraysException && penultimate.value === "}" ||
|
||||
options.arraysInArraysException && penultimate.value === "]" ||
|
||||
options.singleElementException && node.elements.length === 1
|
||||
? !options.spaced : options.spaced;
|
||||
|
||||
if (isSameLine(first, second)) {
|
||||
if (openingBracketMustBeSpaced && !isSpaced(first, second)) {
|
||||
reportRequiredBeginningSpace(node, first);
|
||||
}
|
||||
if (!openingBracketMustBeSpaced && isSpaced(first, second)) {
|
||||
reportNoBeginningSpace(node, first);
|
||||
}
|
||||
}
|
||||
|
||||
if (isSameLine(penultimate, last)) {
|
||||
if (closingBracketMustBeSpaced && !isSpaced(penultimate, last)) {
|
||||
reportRequiredEndingSpace(node, last);
|
||||
}
|
||||
if (!closingBracketMustBeSpaced && isSpaced(penultimate, last)) {
|
||||
reportNoEndingSpace(node, last);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ArrayPattern: validateArraySpacing,
|
||||
ArrayExpression: validateArraySpacing
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["always", "never"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"singleValue": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"objectsInArrays": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"arraysInArrays": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
@ -316,3 +316,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -33,6 +33,15 @@ module.exports = function(context) {
|
||||
return node && node.type === "BlockStatement";
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the token is an punctuator with a value of curly brace
|
||||
* @param {object} token - Token to check
|
||||
* @returns {boolean} true if its a curly punctuator
|
||||
* @private
|
||||
*/
|
||||
function isCurlyPunctuator(token) {
|
||||
return token.value === "{" || token.value === "}";
|
||||
}
|
||||
|
||||
/**
|
||||
* Binds a list of properties to a function that verifies that the opening
|
||||
@ -97,7 +106,7 @@ module.exports = function(context) {
|
||||
tokens = context.getTokensBefore(node.alternate, 2);
|
||||
|
||||
if (style === "1tbs") {
|
||||
if (tokens[0].loc.start.line !== tokens[1].loc.start.line) {
|
||||
if (tokens[0].loc.start.line !== tokens[1].loc.start.line && isCurlyPunctuator(tokens[0]) ) {
|
||||
context.report(node.alternate, CLOSE_MESSAGE);
|
||||
}
|
||||
} else if (style === "stroustrup") {
|
||||
@ -202,3 +211,18 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["1tbs", "stroustrup"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allowSingleLine": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -97,3 +97,15 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"properties": {
|
||||
"enum": ["always", "never"]
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -25,7 +25,7 @@ module.exports = function (context) {
|
||||
function checkForTrailingComma(node) {
|
||||
var items = node.properties || node.elements,
|
||||
length = items.length,
|
||||
nodeIsMultiLine = node.loc.start.line !== node.loc.end.line,
|
||||
lastTokenOnNewLine,
|
||||
lastItem,
|
||||
penultimateToken,
|
||||
hasDanglingComma;
|
||||
@ -39,9 +39,10 @@ module.exports = function (context) {
|
||||
if (forbidDangle && hasDanglingComma) {
|
||||
context.report(lastItem, penultimateToken.loc.start, UNEXPECTED_MESSAGE);
|
||||
} else if (allowDangle === "always-multiline") {
|
||||
if (hasDanglingComma && !nodeIsMultiLine) {
|
||||
lastTokenOnNewLine = node.loc.end.line !== penultimateToken.loc.end.line;
|
||||
if (hasDanglingComma && !lastTokenOnNewLine) {
|
||||
context.report(lastItem, penultimateToken.loc.start, UNEXPECTED_MESSAGE);
|
||||
} else if (!hasDanglingComma && nodeIsMultiLine) {
|
||||
} else if (!hasDanglingComma && lastTokenOnNewLine) {
|
||||
context.report(lastItem, penultimateToken.loc.end, MISSING_MESSAGE);
|
||||
}
|
||||
} else if (allowDangle === "always" && !hasDanglingComma) {
|
||||
@ -56,3 +57,9 @@ module.exports = function (context) {
|
||||
"ArrayExpression": checkForTrailingComma
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["always", "always-multiline", "never"]
|
||||
}
|
||||
];
|
||||
|
@ -22,6 +22,22 @@ module.exports = function(context) {
|
||||
|
||||
// the index of the last comment that was checked
|
||||
var lastCommentIndex = 0;
|
||||
var allComments;
|
||||
|
||||
/**
|
||||
* Determines the length of comment between 2 tokens
|
||||
* @param {Object} left - The left token object.
|
||||
* @param {Object} right - The right token object.
|
||||
* @returns {number} Length of comment in between tokens
|
||||
*/
|
||||
function getCommentLengthBetweenTokens(left, right) {
|
||||
return allComments.reduce(function(val, comment) {
|
||||
if (left.range[1] <= comment.range[0] && comment.range[1] <= right.range[0]) {
|
||||
val = val + comment.range[1] - comment.range[0];
|
||||
}
|
||||
return val;
|
||||
}, 0);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two adjacent tokens have whitespace between them.
|
||||
@ -31,7 +47,8 @@ module.exports = function(context) {
|
||||
*/
|
||||
function isSpaced(left, right) {
|
||||
var punctuationLength = context.getTokensBetween(left, right).length; // the length of any parenthesis
|
||||
return (left.range[1] + punctuationLength) < right.range[0];
|
||||
var commentLenth = getCommentLengthBetweenTokens(left, right);
|
||||
return (left.range[1] + punctuationLength + commentLenth) < right.range[0];
|
||||
}
|
||||
|
||||
/**
|
||||
@ -130,12 +147,12 @@ module.exports = function(context) {
|
||||
"Program": function() {
|
||||
|
||||
var source = context.getSource(),
|
||||
allComments = context.getAllComments(),
|
||||
pattern = /,/g,
|
||||
commaToken,
|
||||
previousToken,
|
||||
nextToken;
|
||||
|
||||
allComments = context.getAllComments();
|
||||
while (pattern.test(source)) {
|
||||
|
||||
// do not flag anything inside of comments
|
||||
@ -157,3 +174,18 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"before": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"after": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -175,3 +175,21 @@ module.exports = function(context) {
|
||||
|
||||
return nodes;
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["first", "last"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exceptions": {
|
||||
"type": "object",
|
||||
"additionalProperties": {
|
||||
"type": "boolean"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -86,3 +86,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
];
|
||||
|
144
tools/eslint/lib/rules/computed-property-spacing.js
Normal file
144
tools/eslint/lib/rules/computed-property-spacing.js
Normal file
@ -0,0 +1,144 @@
|
||||
/**
|
||||
* @fileoverview Disallows or enforces spaces inside computed properties.
|
||||
* @author Jamund Ferguson
|
||||
* @copyright 2015 Jamund Ferguson. All rights reserved.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var propertyNameMustBeSpaced = context.options[0] === "always"; // default is "never"
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines whether two adjacent tokens are have whitespace between them.
|
||||
* @param {Object} left - The left token object.
|
||||
* @param {Object} right - The right token object.
|
||||
* @returns {boolean} Whether or not there is space between the tokens.
|
||||
*/
|
||||
function isSpaced(left, right) {
|
||||
return left.range[1] < right.range[0];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines whether two adjacent tokens are on the same line.
|
||||
* @param {Object} left - The left token object.
|
||||
* @param {Object} right - The right token object.
|
||||
* @returns {boolean} Whether or not the tokens are on the same line.
|
||||
*/
|
||||
function isSameLine(left, right) {
|
||||
return left.loc.start.line === right.loc.start.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space after the first token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoBeginningSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"There should be no space after '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there shouldn't be a space before the last token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportNoEndingSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"There should be no space before '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space after the first token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredBeginningSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"A space is required after '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports that there should be a space before the last token
|
||||
* @param {ASTNode} node - The node to report in the event of an error.
|
||||
* @param {Token} token - The token to use for the report.
|
||||
* @returns {void}
|
||||
*/
|
||||
function reportRequiredEndingSpace(node, token) {
|
||||
context.report(node, token.loc.start,
|
||||
"A space is required before '" + token.value + "'");
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that checks the spacing of a node on the property name
|
||||
* that was passed in.
|
||||
* @param {String} propertyName The property on the node to check for spacing
|
||||
* @returns {Function} A function that will check spacing on a node
|
||||
*/
|
||||
function checkSpacing(propertyName) {
|
||||
return function(node) {
|
||||
if (!node.computed) {
|
||||
return;
|
||||
}
|
||||
|
||||
var property = node[propertyName];
|
||||
|
||||
var before = context.getTokenBefore(property),
|
||||
first = context.getFirstToken(property),
|
||||
last = context.getLastToken(property),
|
||||
after = context.getTokenAfter(property);
|
||||
|
||||
if (isSameLine(before, first)) {
|
||||
if (propertyNameMustBeSpaced) {
|
||||
if (!isSpaced(before, first) && isSameLine(before, first)) {
|
||||
reportRequiredBeginningSpace(node, before);
|
||||
}
|
||||
} else {
|
||||
if (isSpaced(before, first)) {
|
||||
reportNoBeginningSpace(node, before);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
if (isSameLine(last, after)) {
|
||||
if (propertyNameMustBeSpaced) {
|
||||
if (!isSpaced(last, after) && isSameLine(last, after)) {
|
||||
reportRequiredEndingSpace(node, after);
|
||||
}
|
||||
} else {
|
||||
if (isSpaced(last, after)) {
|
||||
reportNoEndingSpace(node, after);
|
||||
}
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
Property: checkSpacing("key"),
|
||||
MemberExpression: checkSpacing("property")
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["always", "never"]
|
||||
}
|
||||
];
|
@ -71,3 +71,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -2,6 +2,7 @@
|
||||
* @fileoverview Rule to enforce consistent naming of "this" context variables
|
||||
* @author Raphael Pigulla
|
||||
* @copyright 2015 Timothy Jones. All rights reserved.
|
||||
* @copyright 2015 David Aurelio. All rights reserved.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
@ -93,8 +94,12 @@ module.exports = function(context) {
|
||||
"FunctionDeclaration:exit": ensureWasAssigned,
|
||||
|
||||
"VariableDeclarator": function (node) {
|
||||
if (node.init !== null) {
|
||||
checkAssignment(node, node.id.name, node.init);
|
||||
var id = node.id;
|
||||
var isDestructuring =
|
||||
id.type === "ArrayPattern" || id.type === "ObjectPattern";
|
||||
|
||||
if (node.init !== null && !isDestructuring) {
|
||||
checkAssignment(node, id.name, node.init);
|
||||
}
|
||||
},
|
||||
|
||||
@ -106,3 +111,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
];
|
||||
|
108
tools/eslint/lib/rules/constructor-super.js
Normal file
108
tools/eslint/lib/rules/constructor-super.js
Normal file
@ -0,0 +1,108 @@
|
||||
/**
|
||||
* @fileoverview A rule to verify `super()` callings in constructor.
|
||||
* @author Toru Nagashima
|
||||
* @copyright 2015 Toru Nagashima. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
/**
|
||||
* Searches a class node from ancestors of a node.
|
||||
* @param {Node} node - A node to get.
|
||||
* @returns {ClassDeclaration|ClassExpression|null} the found class node or `null`.
|
||||
*/
|
||||
function getClassInAncestor(node) {
|
||||
while (node != null) {
|
||||
if (node.type === "ClassDeclaration" || node.type === "ClassExpression") {
|
||||
return node;
|
||||
}
|
||||
node = node.parent;
|
||||
}
|
||||
/* istanbul ignore next */
|
||||
return null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a node is the null literal.
|
||||
* @param {Node} node - A node to check.
|
||||
* @returns {boolean} whether or not a node is the null literal.
|
||||
*/
|
||||
function isNullLiteral(node) {
|
||||
return node != null && node.type === "Literal" && node.value === null;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not the current traversal context is on constructors.
|
||||
* @param {{scope: Scope}} item - A checking context to check.
|
||||
* @returns {boolean} whether or not the current traversal context is on constructors.
|
||||
*/
|
||||
function isOnConstructor(item) {
|
||||
return item != null && item.scope === context.getScope().variableScope.upper.variableScope;
|
||||
}
|
||||
|
||||
// A stack for checking context.
|
||||
var stack = [];
|
||||
|
||||
return {
|
||||
/**
|
||||
* Start checking.
|
||||
* @param {MethodDefinition} node - A target node.
|
||||
* @returns {void}
|
||||
*/
|
||||
"MethodDefinition": function(node) {
|
||||
if (node.kind !== "constructor") {
|
||||
return;
|
||||
}
|
||||
stack.push({
|
||||
superCallings: [],
|
||||
scope: context.getScope().variableScope
|
||||
});
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks the result, then reports invalid/missing `super()`.
|
||||
* @param {MethodDefinition} node - A target node.
|
||||
* @returns {void}
|
||||
*/
|
||||
"MethodDefinition:exit": function(node) {
|
||||
if (node.kind !== "constructor") {
|
||||
return;
|
||||
}
|
||||
var result = stack.pop();
|
||||
|
||||
var classNode = getClassInAncestor(node);
|
||||
/* istanbul ignore if */
|
||||
if (classNode == null) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (classNode.superClass === null || isNullLiteral(classNode.superClass)) {
|
||||
result.superCallings.forEach(function(superCalling) {
|
||||
context.report(superCalling, "unexpected `super()`.");
|
||||
});
|
||||
} else if (result.superCallings.length === 0) {
|
||||
context.report(node.key, "this constructor requires `super()`.");
|
||||
}
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks the result of checking, then reports invalid/missing `super()`.
|
||||
* @param {MethodDefinition} node - A target node.
|
||||
* @returns {void}
|
||||
*/
|
||||
"CallExpression": function(node) {
|
||||
var item = stack[stack.length - 1];
|
||||
if (isOnConstructor(item) && node.callee.type === "Super") {
|
||||
item.superCallings.push(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
@ -101,3 +101,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["all", "multi", "multi-line"]
|
||||
}
|
||||
];
|
||||
|
@ -62,3 +62,5 @@ module.exports = function(context) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
63
tools/eslint/lib/rules/dot-location.js
Normal file
63
tools/eslint/lib/rules/dot-location.js
Normal file
@ -0,0 +1,63 @@
|
||||
/**
|
||||
* @fileoverview Validates newlines before and after dots
|
||||
* @author Greg Cochard
|
||||
* @copyright 2015 Greg Cochard
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function (context) {
|
||||
|
||||
var config = context.options[0],
|
||||
// default to onObject if no preference is passed
|
||||
onObject = config === "object" || !config;
|
||||
|
||||
/**
|
||||
* Checks whether two tokens are on the same line.
|
||||
* @param {Object} left The leftmost token.
|
||||
* @param {Object} right The rightmost token.
|
||||
* @returns {boolean} True if the tokens are on the same line, false if not.
|
||||
* @private
|
||||
*/
|
||||
function isSameLine(left, right) {
|
||||
return left.loc.end.line === right.loc.start.line;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports if the dot between object and property is on the correct loccation.
|
||||
* @param {ASTNode} obj The object owning the property.
|
||||
* @param {ASTNode} prop The property of the object.
|
||||
* @param {ASTNode} node The corresponding node of the token.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkDotLocation(obj, prop, node) {
|
||||
var dot = context.getTokenBefore(prop);
|
||||
|
||||
if (dot.type === "Punctuator" && dot.value === ".") {
|
||||
if (onObject) {
|
||||
if (!isSameLine(obj, dot)) {
|
||||
context.report(node, dot.loc.start, "Expected dot to be on same line as object.");
|
||||
}
|
||||
} else if (!isSameLine(dot, prop)) {
|
||||
context.report(node, dot.loc.start, "Expected dot to be on same line as property.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the spacing of the dot within a member expression.
|
||||
* @param {ASTNode} node The node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkNode(node) {
|
||||
checkDotLocation(node.object, node.property, node);
|
||||
}
|
||||
|
||||
return {
|
||||
"MemberExpression": checkNode
|
||||
};
|
||||
};
|
@ -102,3 +102,18 @@ module.exports = function(context) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"allowKeywords": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowPattern": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -34,3 +34,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -88,3 +88,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["smart", "allow-null"]
|
||||
}
|
||||
];
|
||||
|
@ -41,3 +41,5 @@ module.exports = function(context) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -41,3 +41,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["declaration", "expression"]
|
||||
}
|
||||
];
|
||||
|
@ -13,12 +13,17 @@
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var mode = {
|
||||
before: { before: true, after: false },
|
||||
after: { before: false, after: true },
|
||||
both: { before: true, after: true },
|
||||
neither: { before: false, after: false }
|
||||
}[context.options[0] || "before"];
|
||||
var mode = (function(option) {
|
||||
if (option == null || typeof option === "string") {
|
||||
return {
|
||||
before: { before: true, after: false },
|
||||
after: { before: false, after: true },
|
||||
both: { before: true, after: true },
|
||||
neither: { before: false, after: false }
|
||||
}[option || "before"];
|
||||
}
|
||||
return option;
|
||||
}(context.options[0]));
|
||||
|
||||
/**
|
||||
* Checks the spacing between two tokens before or after the star token.
|
||||
@ -48,30 +53,28 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkFunction(node) {
|
||||
var isMethod, starToken, tokenBefore, tokenAfter;
|
||||
var prevToken, starToken, nextToken;
|
||||
|
||||
if (!node.generator) {
|
||||
return;
|
||||
}
|
||||
|
||||
isMethod = !!context.getAncestors().pop().method;
|
||||
|
||||
if (isMethod) {
|
||||
if (node.parent.method || node.parent.type === "MethodDefinition") {
|
||||
starToken = context.getTokenBefore(node, 1);
|
||||
} else {
|
||||
starToken = context.getFirstToken(node, 1);
|
||||
}
|
||||
|
||||
// Only check before when preceded by `function` keyword
|
||||
tokenBefore = context.getTokenBefore(starToken);
|
||||
if (tokenBefore.value === "function") {
|
||||
checkSpacing("before", tokenBefore, starToken);
|
||||
prevToken = context.getTokenBefore(starToken);
|
||||
if (prevToken.value === "function" || prevToken.value === "static") {
|
||||
checkSpacing("before", prevToken, starToken);
|
||||
}
|
||||
|
||||
// Only check after when followed by an identifier
|
||||
tokenAfter = context.getTokenAfter(starToken);
|
||||
if (tokenAfter.type === "Identifier") {
|
||||
checkSpacing("after", starToken, tokenAfter);
|
||||
nextToken = context.getTokenAfter(starToken);
|
||||
if (nextToken.type === "Identifier") {
|
||||
checkSpacing("after", starToken, nextToken);
|
||||
}
|
||||
}
|
||||
|
||||
@ -81,3 +84,21 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": ["before", "after", "both", "neither"]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"before": {"type": "boolean"},
|
||||
"after": {"type": "boolean"}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
]
|
||||
}
|
||||
];
|
||||
|
@ -68,3 +68,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["start", "middle", "end"]
|
||||
}
|
||||
];
|
||||
|
@ -41,3 +41,9 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["always", "never"]
|
||||
}
|
||||
];
|
||||
|
@ -28,3 +28,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -1,6 +1,7 @@
|
||||
/**
|
||||
* @fileoverview Ensure handling of errors when we know they exist.
|
||||
* @author Jamund Ferguson
|
||||
* @copyright 2015 Mathias Schreck.
|
||||
* @copyright 2014 Jamund Ferguson. All rights reserved.
|
||||
*/
|
||||
|
||||
@ -13,8 +14,6 @@
|
||||
module.exports = function(context) {
|
||||
|
||||
var errorArgument = context.options[0] || "err";
|
||||
var callbacks = [];
|
||||
var scopes = 0;
|
||||
|
||||
/**
|
||||
* Checks if the given argument should be interpreted as a regexp pattern.
|
||||
@ -40,44 +39,14 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Check the arguments to see if we need to start tracking the error object.
|
||||
* @param {ASTNode} node The AST node to check.
|
||||
* @returns {void}
|
||||
* Get the parameters of a given function scope.
|
||||
* @param {object} scope The function scope.
|
||||
* @returns {array} All parameters of the given scope.
|
||||
*/
|
||||
function startFunction(node) {
|
||||
|
||||
// keep track of nested scopes
|
||||
scopes++;
|
||||
|
||||
// check if the first argument matches our argument name
|
||||
var firstArg = node.params && node.params[0];
|
||||
if (firstArg && matchesConfiguredErrorName(firstArg.name)) {
|
||||
callbacks.push({handled: false, depth: scopes, errorVariableName: firstArg.name});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* At the end of a function check to see if the error was handled.
|
||||
* @param {ASTNode} node The AST node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function endFunction(node) {
|
||||
|
||||
var callback = callbacks[callbacks.length - 1] || {};
|
||||
|
||||
// check if a callback is ending, if so pop it off the stack
|
||||
if (callback.depth === scopes) {
|
||||
callbacks.pop();
|
||||
|
||||
// check if there were no handled errors since the last callback
|
||||
if (!callback.handled) {
|
||||
context.report(node, "Expected error to be handled.");
|
||||
}
|
||||
}
|
||||
|
||||
// less nested functions
|
||||
scopes--;
|
||||
|
||||
function getParameters(scope) {
|
||||
return scope.variables.filter(function (variable) {
|
||||
return variable.defs[0] && variable.defs[0].type === "Parameter";
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
@ -86,33 +55,27 @@ module.exports = function(context) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForError(node) {
|
||||
if (callbacks.length > 0) {
|
||||
var callback = callbacks[callbacks.length - 1] || {};
|
||||
var scope = context.getScope(),
|
||||
parameters = getParameters(scope),
|
||||
firstParameter = parameters[0];
|
||||
|
||||
// make sure the node's name matches our error argument name
|
||||
var isAboutError = node.name === callback.errorVariableName;
|
||||
|
||||
// we don't consider these use cases as "handling" the error
|
||||
var doNotCount = ["FunctionDeclaration", "ArrowFunctionExpression", "FunctionExpression", "CatchClause"];
|
||||
|
||||
// make sure this identifier isn't used as part of one of them
|
||||
var isHandled = doNotCount.indexOf(node.parent.type) === -1;
|
||||
|
||||
if (isAboutError && isHandled) {
|
||||
// record that this callback handled its error
|
||||
callback.handled = true;
|
||||
if (firstParameter && matchesConfiguredErrorName(firstParameter.name)) {
|
||||
if (firstParameter.references.length === 0) {
|
||||
context.report(node, "Expected error to be handled.");
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"FunctionDeclaration": startFunction,
|
||||
"FunctionExpression": startFunction,
|
||||
"ArrowFunctionExpression": startFunction,
|
||||
"Identifier": checkForError,
|
||||
"FunctionDeclaration:exit": endFunction,
|
||||
"FunctionExpression:exit": endFunction,
|
||||
"ArrowFunctionExpression:exit": endFunction
|
||||
"FunctionDeclaration": checkForError,
|
||||
"FunctionExpression": checkForError,
|
||||
"ArrowFunctionExpression": checkForError
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "string"
|
||||
}
|
||||
];
|
||||
|
@ -46,7 +46,7 @@ module.exports = function (context) {
|
||||
if (context.options[0] === "tab") {
|
||||
indentChar = "\t";
|
||||
indentSize = 1;
|
||||
} else if (typeof context.options[0] === "number") {
|
||||
} else /* istanbul ignore else : this will be caught by options validation */ if (typeof context.options[0] === "number") {
|
||||
indentSize = context.options[0];
|
||||
}
|
||||
|
||||
@ -462,3 +462,25 @@ module.exports = function (context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"oneOf": [
|
||||
{
|
||||
"enum": ["tab"]
|
||||
},
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
]
|
||||
},
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"indentSwitchCase": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -35,7 +35,7 @@ function last(arr) {
|
||||
* @returns {boolean} True if the candidate property is part of the group.
|
||||
*/
|
||||
function continuesPropertyGroup(lastMember, candidate) {
|
||||
var groupEndLine = lastMember.loc.end.line,
|
||||
var groupEndLine = lastMember.loc.start.line,
|
||||
candidateStartLine = candidate.loc.start.line,
|
||||
comments, i;
|
||||
|
||||
@ -305,3 +305,21 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"align": {
|
||||
"enum": ["colon", "value"]
|
||||
},
|
||||
"beforeColon": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"afterColon": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
44
tools/eslint/lib/rules/linebreak-style.js
Normal file
44
tools/eslint/lib/rules/linebreak-style.js
Normal file
@ -0,0 +1,44 @@
|
||||
/**
|
||||
* @fileoverview Rule to forbid mixing LF and LFCR line breaks.
|
||||
* @author Erik Mueller
|
||||
* @copyright 2015 Varun Verma. All rights reserverd.
|
||||
* @copyright 2015 James Whitney. All rights reserved.
|
||||
* @copyright 2015 Erik Mueller. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function (context) {
|
||||
var EXPECTED_LF_MSG = "Expected linebreaks to be 'LF' but found 'CRLF'.",
|
||||
EXPECTED_CRLF_MSG = "Expected linebreaks to be 'CRLF' but found 'LF'.";
|
||||
|
||||
return {
|
||||
"Program": function checkForlinebreakStyle(node) {
|
||||
var linebreakStyle = context.options[0] || "unix",
|
||||
expectedLF = linebreakStyle === "unix",
|
||||
linebreaks = context.getSource().match(/\r\n|\r|\n|\u2028|\u2029/g),
|
||||
lineOfError = -1;
|
||||
|
||||
if (linebreaks !== null) {
|
||||
lineOfError = linebreaks.indexOf(expectedLF ? "\r\n" : "\n");
|
||||
}
|
||||
|
||||
if (lineOfError !== -1) {
|
||||
context.report(node, {
|
||||
line: lineOfError + 1,
|
||||
column: context.getSourceLines()[lineOfError].length
|
||||
}, expectedLF ? EXPECTED_LF_MSG : EXPECTED_CRLF_MSG);
|
||||
}
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["unix", "windows"]
|
||||
}
|
||||
];
|
224
tools/eslint/lib/rules/lines-around-comment.js
Normal file
224
tools/eslint/lib/rules/lines-around-comment.js
Normal file
@ -0,0 +1,224 @@
|
||||
/**
|
||||
* @fileoverview Enforces empty lines around comments.
|
||||
* @author Jamund Ferguson
|
||||
* @copyright 2015 Jamund Ferguson. All rights reserved.
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/**
|
||||
* Return an array with with any line numbers that are empty.
|
||||
* @param {Array} lines An array of each line of the file.
|
||||
* @returns {Array} An array of line numbers.
|
||||
*/
|
||||
function getEmptyLineNums(lines) {
|
||||
var emptyLines = lines.map(function(line, i) {
|
||||
return {
|
||||
code: line.trim(),
|
||||
num: i + 1
|
||||
};
|
||||
}).filter(function(line) {
|
||||
return !line.code;
|
||||
}).map(function(line) {
|
||||
return line.num;
|
||||
});
|
||||
return emptyLines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Return an array with with any line numbers that contain comments.
|
||||
* @param {Array} comments An array of comment nodes.
|
||||
* @returns {Array} An array of line numbers.
|
||||
*/
|
||||
function getCommentLineNums(comments) {
|
||||
var lines = [];
|
||||
comments.forEach(function (token) {
|
||||
var start = token.loc.start.line;
|
||||
var end = token.loc.end.line;
|
||||
lines.push(start, end);
|
||||
});
|
||||
return lines;
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a value is an array.
|
||||
* @param {number} val The value we wish to check for in the array..
|
||||
* @param {Array} array An array.
|
||||
* @returns {boolean} True if the value is in the array..
|
||||
*/
|
||||
function contains(val, array) {
|
||||
return array.indexOf(val) > -1;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var options = context.options[0] || {};
|
||||
options.beforeLineComment = options.beforeLineComment || false;
|
||||
options.afterLineComment = options.afterLineComment || false;
|
||||
options.beforeBlockComment = typeof options.beforeBlockComment !== "undefined" ? options.beforeBlockComment : true;
|
||||
options.afterBlockComment = options.afterBlockComment || false;
|
||||
options.allowBlockStart = options.allowBlockStart || false;
|
||||
options.allowBlockEnd = options.allowBlockEnd || false;
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are not on lines starting with or ending with code
|
||||
* @param {ASTNode} node The comment node to check.
|
||||
* @returns {boolean} True if the comment is not alone.
|
||||
*/
|
||||
function codeAroundComment(node) {
|
||||
|
||||
var lines = context.getSourceLines();
|
||||
|
||||
// Get the whole line and cut it off at the start of the comment
|
||||
var startLine = lines[node.loc.start.line - 1];
|
||||
var endLine = lines[node.loc.end.line - 1];
|
||||
|
||||
var preamble = startLine.slice(0, node.loc.start.column).trim();
|
||||
|
||||
// Also check after the comment
|
||||
var postamble = endLine.slice(node.loc.end.column).trim();
|
||||
|
||||
// Should be false if there was only whitespace around the comment
|
||||
return !!(preamble || postamble);
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the block start or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @returns {boolean} True if the comment is at block start.
|
||||
*/
|
||||
function isCommentAtBlockStart(node) {
|
||||
var ancestors = context.getAncestors();
|
||||
var parent;
|
||||
|
||||
if (ancestors.length) {
|
||||
parent = ancestors.pop();
|
||||
}
|
||||
return parent && (parent.type === "BlockStatement" || parent.body.type === "BlockStatement") &&
|
||||
node.loc.start.line - parent.loc.start.line === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns whether or not comments are at the block end or not.
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @returns {boolean} True if the comment is at block end.
|
||||
*/
|
||||
function isCommentAtBlockEnd(node) {
|
||||
var ancestors = context.getAncestors();
|
||||
var parent;
|
||||
|
||||
if (ancestors.length) {
|
||||
parent = ancestors.pop();
|
||||
}
|
||||
return parent && (parent.type === "BlockStatement" || parent.body.type === "BlockStatement") &&
|
||||
parent.loc.end.line - node.loc.end.line === 1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a comment node has lines around it (ignores inline comments)
|
||||
* @param {ASTNode} node The Comment node.
|
||||
* @param {Object} opts Options to determine the newline.
|
||||
* @param {Boolean} opts.after Should have a newline after this line.
|
||||
* @param {Boolean} opts.before Should have a newline before this line.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForEmptyLine(node, opts) {
|
||||
|
||||
var lines = context.getSourceLines(),
|
||||
numLines = lines.length + 1,
|
||||
comments = context.getAllComments(),
|
||||
commentLines = getCommentLineNums(comments),
|
||||
emptyLines = getEmptyLineNums(lines),
|
||||
commentAndEmptyLines = commentLines.concat(emptyLines);
|
||||
|
||||
var after = opts.after,
|
||||
before = opts.before;
|
||||
|
||||
var prevLineNum = node.loc.start.line - 1,
|
||||
nextLineNum = node.loc.end.line + 1,
|
||||
commentIsNotAlone = codeAroundComment(node);
|
||||
|
||||
var blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node),
|
||||
blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node);
|
||||
|
||||
// ignore top of the file and bottom of the file
|
||||
if (prevLineNum < 1) {
|
||||
before = false;
|
||||
}
|
||||
if (nextLineNum >= numLines) {
|
||||
after = false;
|
||||
}
|
||||
|
||||
// we ignore all inline comments
|
||||
if (commentIsNotAlone) {
|
||||
return;
|
||||
}
|
||||
|
||||
// check for newline before
|
||||
if (!blockStartAllowed && before && !contains(prevLineNum, commentAndEmptyLines)) {
|
||||
context.report(node, "Expected line before comment.");
|
||||
}
|
||||
|
||||
// check for newline after
|
||||
if (!blockEndAllowed && after && !contains(nextLineNum, commentAndEmptyLines)) {
|
||||
context.report(node, "Expected line after comment.");
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
|
||||
"LineComment": function(node) {
|
||||
if (options.beforeLineComment || options.afterLineComment) {
|
||||
checkForEmptyLine(node, {
|
||||
after: options.afterLineComment,
|
||||
before: options.beforeLineComment
|
||||
});
|
||||
}
|
||||
},
|
||||
|
||||
"BlockComment": function(node) {
|
||||
if (options.beforeBlockComment || options.afterBlockComment) {
|
||||
checkForEmptyLine(node, {
|
||||
after: options.afterBlockComment,
|
||||
before: options.beforeBlockComment
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"beforeBlockComment": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"afterBlockComment": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"beforeLineComment": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"afterLineComment": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowBlockStart": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"allowBlockEnd": {
|
||||
"type": "boolean"
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
@ -81,3 +81,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
];
|
||||
|
@ -32,9 +32,9 @@ module.exports = function(context) {
|
||||
return result;
|
||||
}
|
||||
|
||||
var tabWidth = context.options[1];
|
||||
var tabWidth = context.options[1] || 4;
|
||||
|
||||
var maxLength = context.options[0],
|
||||
var maxLength = context.options[0] || 80,
|
||||
tabString = stringRepeat(" ", tabWidth);
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
@ -63,3 +63,14 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
},
|
||||
{
|
||||
"type": "integer",
|
||||
"minimum": 0
|
||||
}
|
||||
];
|
||||
|
@ -65,3 +65,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
];
|
||||
|
@ -37,3 +37,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
];
|
||||
|
@ -53,3 +53,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "integer"
|
||||
}
|
||||
];
|
||||
|
@ -67,8 +67,8 @@ function calculateCapIsNewExceptions(config) {
|
||||
module.exports = function(context) {
|
||||
|
||||
var config = context.options[0] || {};
|
||||
config.newIsCap = config.newIsCap === false ? false : true;
|
||||
config.capIsNew = config.capIsNew === false ? false : true;
|
||||
config.newIsCap = config.newIsCap !== false;
|
||||
config.capIsNew = config.capIsNew !== false;
|
||||
|
||||
var newIsCapExceptions = checkArray(config, "newIsCapExceptions", []).reduce(invert, {});
|
||||
|
||||
@ -195,3 +195,30 @@ module.exports = function(context) {
|
||||
|
||||
return listeners;
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"newIsCap": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"capIsNew": {
|
||||
"type": "boolean"
|
||||
},
|
||||
"newIsCapExceptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
},
|
||||
"capIsNewExceptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
}
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -25,3 +25,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -119,3 +119,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["never", "always"]
|
||||
}
|
||||
];
|
||||
|
@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag use of alert, confirm, prompt
|
||||
* @author Nicholas C. Zakas
|
||||
* @copyright 2015 Mathias Schreck
|
||||
* @copyright 2013 Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
@ -8,47 +10,144 @@
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
function matchProhibited(name) {
|
||||
return name.match(/^(alert|confirm|prompt)$/);
|
||||
/**
|
||||
* Checks if the given name is a prohibited identifier.
|
||||
* @param {string} name The name to check
|
||||
* @returns {boolean} Whether or not the name is prohibited.
|
||||
*/
|
||||
function isProhibitedIdentifier(name) {
|
||||
return /^(alert|confirm|prompt)$/.test(name);
|
||||
}
|
||||
|
||||
function report(context, node, result) {
|
||||
context.report(node, "Unexpected {{name}}.", { name: result[1] });
|
||||
/**
|
||||
* Reports the given node and identifier name.
|
||||
* @param {RuleContext} context The ESLint rule context.
|
||||
* @param {ASTNode} node The node to report on.
|
||||
* @param {string} identifierName The name of the identifier.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(context, node, identifierName) {
|
||||
context.report(node, "Unexpected {{name}}.", { name: identifierName });
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns the property name of a MemberExpression.
|
||||
* @param {ASTNode} memberExpressionNode The MemberExpression node.
|
||||
* @returns {string|undefined} Returns the property name if available, undefined else.
|
||||
*/
|
||||
function getPropertyName(memberExpressionNode) {
|
||||
if (memberExpressionNode.computed) {
|
||||
if (memberExpressionNode.property.type === "Literal") {
|
||||
return memberExpressionNode.property.value;
|
||||
}
|
||||
} else {
|
||||
return memberExpressionNode.property.name;
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Finds the escope reference in the given scope.
|
||||
* @param {Object} scope The scope to search.
|
||||
* @param {ASTNode} node The identifier node.
|
||||
* @returns {Reference|undefined} Returns the found reference or undefined if none were found.
|
||||
*/
|
||||
function findReference(scope, node) {
|
||||
var references = scope.references.filter(function (reference) {
|
||||
return reference.identifier.range[0] === node.range[0] &&
|
||||
reference.identifier.range[1] === node.range[1];
|
||||
});
|
||||
|
||||
if (references.length === 1) {
|
||||
return references[0];
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given identifier name is shadowed in the given global scope.
|
||||
* @param {Object} globalScope The global scope.
|
||||
* @param {string} identifierName The identifier name to check
|
||||
* @returns {boolean} Whether or not the name is shadowed globally.
|
||||
*/
|
||||
function isGloballyShadowed(globalScope, identifierName) {
|
||||
return globalScope.variables.some(function (variable) {
|
||||
return variable.name === identifierName && variable.defs.length > 0;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given identifier node is shadowed in the given scope.
|
||||
* @param {Object} scope The current scope.
|
||||
* @param {Object} globalScope The global scope.
|
||||
* @param {string} node The identifier node to check
|
||||
* @returns {boolean} Whether or not the name is shadowed.
|
||||
*/
|
||||
function isShadowed(scope, globalScope, node) {
|
||||
var reference = findReference(scope, node),
|
||||
identifierName = node.name;
|
||||
|
||||
if (reference) {
|
||||
if (reference.resolved || isGloballyShadowed(globalScope, identifierName)) {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if the given identifier node is a ThisExpression in the global scope or the global window property.
|
||||
* @param {Object} scope The current scope.
|
||||
* @param {Object} globalScope The global scope.
|
||||
* @param {string} node The identifier node to check
|
||||
* @returns {boolean} Whether or not the node is a reference to the global object.
|
||||
*/
|
||||
function isGlobalThisReferenceOrGlobalWindow(scope, globalScope, node) {
|
||||
if (scope.type === "global" && node.type === "ThisExpression") {
|
||||
return true;
|
||||
} else if (node.name === "window") {
|
||||
return !isShadowed(scope, globalScope, node);
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var globalScope;
|
||||
|
||||
return {
|
||||
|
||||
"CallExpression": function(node) {
|
||||
"Program": function () {
|
||||
globalScope = context.getScope();
|
||||
},
|
||||
|
||||
var result;
|
||||
"CallExpression": function(node) {
|
||||
var callee = node.callee,
|
||||
identifierName,
|
||||
currentScope = context.getScope();
|
||||
|
||||
// without window.
|
||||
if (node.callee.type === "Identifier") {
|
||||
if (callee.type === "Identifier") {
|
||||
identifierName = callee.name;
|
||||
|
||||
result = matchProhibited(node.callee.name);
|
||||
|
||||
if (result) {
|
||||
report(context, node, result);
|
||||
if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) {
|
||||
report(context, node, identifierName);
|
||||
}
|
||||
|
||||
} else if (node.callee.type === "MemberExpression" && node.callee.property.type === "Identifier") {
|
||||
} else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) {
|
||||
identifierName = getPropertyName(callee);
|
||||
|
||||
result = matchProhibited(node.callee.property.name);
|
||||
|
||||
if (result && node.callee.object.name === "window") {
|
||||
report(context, node, result);
|
||||
if (isProhibitedIdentifier(identifierName)) {
|
||||
report(context, node, identifierName);
|
||||
}
|
||||
|
||||
}
|
||||
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -27,3 +27,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -53,3 +53,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -25,3 +25,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -48,3 +48,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -41,3 +41,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -115,3 +115,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["except-parens", "always"]
|
||||
}
|
||||
];
|
||||
|
@ -23,3 +23,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -69,3 +69,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -19,3 +19,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -54,3 +54,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -18,3 +18,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -21,3 +21,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -23,3 +23,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -57,7 +57,11 @@ module.exports = function(context) {
|
||||
|
||||
case "ArrayPattern":
|
||||
param.elements.forEach(function(element) {
|
||||
markParam(element.name);
|
||||
|
||||
// Arrays can be sparse (unwanted arguments)
|
||||
if (element !== null) {
|
||||
markParam(element.name);
|
||||
}
|
||||
});
|
||||
break;
|
||||
|
||||
@ -81,3 +85,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -39,3 +39,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -25,6 +25,12 @@ module.exports = function(context) {
|
||||
return node.type + typeof node.name + node.name;
|
||||
} else if (node.type === "MemberExpression") {
|
||||
return node.type + getHash(node.object) + getHash(node.property);
|
||||
} else if (node.type === "CallExpression") {
|
||||
return node.type + getHash(node.callee) + node.arguments.map(getHash).join("");
|
||||
} else if (node.type === "BinaryExpression") {
|
||||
return node.type + getHash(node.left) + node.operator + getHash(node.right);
|
||||
} else if (node.type === "ConditionalExpression") {
|
||||
return node.type + getHash(node.test) + getHash(node.consequent) + getHash(node.alternate);
|
||||
}
|
||||
}
|
||||
|
||||
@ -57,3 +63,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -121,3 +121,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
43
tools/eslint/lib/rules/no-empty-character-class.js
Normal file
43
tools/eslint/lib/rules/no-empty-character-class.js
Normal file
@ -0,0 +1,43 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag the use of empty character classes in regular expressions
|
||||
* @author Ian Christian Myers
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/*
|
||||
plain-English description of the following regexp:
|
||||
0. `^` fix the match at the beginning of the string
|
||||
1. `\/`: the `/` that begins the regexp
|
||||
2. `([^\\[]|\\.|\[([^\\\]]|\\.)+\])*`: regexp contents; 0 or more of the following
|
||||
2.0. `[^\\[]`: any character that's not a `\` or a `[` (anything but escape sequences and character classes)
|
||||
2.1. `\\.`: an escape sequence
|
||||
2.2. `\[([^\\\]]|\\.)+\]`: a character class that isn't empty
|
||||
3. `\/` the `/` that ends the regexp
|
||||
4. `[gimuy]*`: optional regexp flags
|
||||
5. `$`: fix the match at the end of the string
|
||||
*/
|
||||
var regex = /^\/([^\\[]|\\.|\[([^\\\]]|\\.)+\])*\/[gimuy]*$/;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
return {
|
||||
|
||||
"Literal": function(node) {
|
||||
var token = context.getFirstToken(node);
|
||||
if (token.type === "RegularExpression" && !regex.test(token.value)) {
|
||||
context.report(node, "Empty class.");
|
||||
}
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
};
|
@ -41,3 +41,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -23,3 +23,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -45,3 +45,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -25,3 +25,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -22,3 +22,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -38,3 +38,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -21,6 +21,16 @@ var BUILTINS = [
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var config = context.options[0] || {};
|
||||
var exceptions = config.exceptions || [];
|
||||
var modifiedBuiltins = BUILTINS;
|
||||
|
||||
if (exceptions.length) {
|
||||
modifiedBuiltins = BUILTINS.filter(function(builtIn) {
|
||||
return exceptions.indexOf(builtIn) === -1;
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
// handle the Array.prototype.extra style case
|
||||
@ -39,7 +49,7 @@ module.exports = function(context) {
|
||||
return;
|
||||
}
|
||||
|
||||
BUILTINS.forEach(function(builtin) {
|
||||
modifiedBuiltins.forEach(function(builtin) {
|
||||
if (lhs.object.object.name === builtin) {
|
||||
context.report(node, builtin + " prototype is read only, properties should not be added.");
|
||||
}
|
||||
@ -64,7 +74,7 @@ module.exports = function(context) {
|
||||
|
||||
if (object &&
|
||||
object.type === "Identifier" &&
|
||||
(BUILTINS.indexOf(object.name) > -1) &&
|
||||
(modifiedBuiltins.indexOf(object.name) > -1) &&
|
||||
subject.property.name === "prototype") {
|
||||
|
||||
context.report(node, object.name + " prototype is read only, properties should not be added.");
|
||||
@ -75,3 +85,19 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "object",
|
||||
"properties": {
|
||||
"exceptions": {
|
||||
"type": "array",
|
||||
"items": {
|
||||
"type": "string"
|
||||
},
|
||||
"uniqueItems": true
|
||||
}
|
||||
},
|
||||
"additionalProperties": false
|
||||
}
|
||||
];
|
||||
|
@ -77,3 +77,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -67,3 +67,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -1,5 +1,5 @@
|
||||
/**
|
||||
* @fileoverview Disallow parenthesesisng higher precedence subexpressions.
|
||||
* @fileoverview Disallow parenthesising higher precedence subexpressions.
|
||||
* @author Michael Ficarra
|
||||
* @copyright 2014 Michael Ficarra. All rights reserved.
|
||||
*/
|
||||
@ -11,6 +11,24 @@
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var ALL_NODES = context.options[0] !== "functions";
|
||||
|
||||
/**
|
||||
* Determines if this rule should be enforced for a node given the current configuration.
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
* @returns {boolean} True if the rule should be enforced for this node.
|
||||
* @private
|
||||
*/
|
||||
function ruleApplies(node) {
|
||||
return ALL_NODES || node.type === "FunctionExpression" || node.type === "ArrowFunctionExpression";
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is surrounded by parentheses.
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
* @returns {boolean} True if the node is parenthesised.
|
||||
* @private
|
||||
*/
|
||||
function isParenthesised(node) {
|
||||
var previousToken = context.getTokenBefore(node),
|
||||
nextToken = context.getTokenAfter(node);
|
||||
@ -20,6 +38,12 @@ module.exports = function(context) {
|
||||
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is surrounded by parentheses twice.
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
* @returns {boolean} True if the node is doubly parenthesised.
|
||||
* @private
|
||||
*/
|
||||
function isParenthesisedTwice(node) {
|
||||
var previousToken = context.getTokenBefore(node, 1),
|
||||
nextToken = context.getTokenAfter(node, 1);
|
||||
@ -29,6 +53,27 @@ module.exports = function(context) {
|
||||
nextToken.value === ")" && nextToken.range[0] >= node.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node is surrounded by (potentially) invalid parentheses.
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
* @returns {boolean} True if the node is incorrectly parenthesised.
|
||||
* @private
|
||||
*/
|
||||
function hasExcessParens(node) {
|
||||
return ruleApplies(node) && isParenthesised(node);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a node that is expected to be parenthesised is surrounded by
|
||||
* (potentially) invalid extra parentheses.
|
||||
* @param {ASTNode} node - The node to be checked.
|
||||
* @returns {boolean} True if the node is has an unexpected extra pair of parentheses.
|
||||
* @private
|
||||
*/
|
||||
function hasDoubleExcessParens(node) {
|
||||
return ruleApplies(node) && isParenthesisedTwice(node);
|
||||
}
|
||||
|
||||
function precedence(node) {
|
||||
|
||||
switch (node.type) {
|
||||
@ -110,23 +155,27 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
function dryUnaryUpdate(node) {
|
||||
if (isParenthesised(node.argument) && precedence(node.argument) >= precedence(node)) {
|
||||
if (hasExcessParens(node.argument) && precedence(node.argument) >= precedence(node)) {
|
||||
report(node.argument);
|
||||
}
|
||||
}
|
||||
|
||||
function dryCallNew(node) {
|
||||
if (isParenthesised(node.callee) && precedence(node.callee) >= precedence(node) &&
|
||||
!(node.type === "CallExpression" && node.callee.type === "FunctionExpression")) {
|
||||
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
|
||||
node.type === "CallExpression" &&
|
||||
node.callee.type === "FunctionExpression" &&
|
||||
// One set of parentheses are allowed for a function expression
|
||||
!hasDoubleExcessParens(node.callee)
|
||||
)) {
|
||||
report(node.callee);
|
||||
}
|
||||
if (node.arguments.length === 1) {
|
||||
if (isParenthesisedTwice(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) {
|
||||
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(node.arguments[0]);
|
||||
}
|
||||
} else {
|
||||
[].forEach.call(node.arguments, function(arg) {
|
||||
if (isParenthesised(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) {
|
||||
if (hasExcessParens(arg) && precedence(arg) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(arg);
|
||||
}
|
||||
});
|
||||
@ -135,10 +184,10 @@ module.exports = function(context) {
|
||||
|
||||
function dryBinaryLogical(node) {
|
||||
var prec = precedence(node);
|
||||
if (isParenthesised(node.left) && precedence(node.left) >= prec) {
|
||||
if (hasExcessParens(node.left) && precedence(node.left) >= prec) {
|
||||
report(node.left);
|
||||
}
|
||||
if (isParenthesised(node.right) && precedence(node.right) > prec) {
|
||||
if (hasExcessParens(node.right) && precedence(node.right) > prec) {
|
||||
report(node.right);
|
||||
}
|
||||
}
|
||||
@ -146,80 +195,91 @@ module.exports = function(context) {
|
||||
return {
|
||||
"ArrayExpression": function(node) {
|
||||
[].forEach.call(node.elements, function(e) {
|
||||
if (e && isParenthesised(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) {
|
||||
if (e && hasExcessParens(e) && precedence(e) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
"ArrowFunctionExpression": function(node) {
|
||||
if (node.body.type !== "BlockStatement" && isParenthesised(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(node.body);
|
||||
if (node.body.type !== "BlockStatement") {
|
||||
if (node.body.type !== "ObjectExpression" && hasExcessParens(node.body) && precedence(node.body) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(node.body);
|
||||
return;
|
||||
}
|
||||
|
||||
// Object literals *must* be parenthesized
|
||||
if (node.body.type === "ObjectExpression" && hasDoubleExcessParens(node.body)) {
|
||||
report(node.body);
|
||||
return;
|
||||
}
|
||||
}
|
||||
},
|
||||
"AssignmentExpression": function(node) {
|
||||
if (isParenthesised(node.right) && precedence(node.right) >= precedence(node)) {
|
||||
if (hasExcessParens(node.right) && precedence(node.right) >= precedence(node)) {
|
||||
report(node.right);
|
||||
}
|
||||
},
|
||||
"BinaryExpression": dryBinaryLogical,
|
||||
"CallExpression": dryCallNew,
|
||||
"ConditionalExpression": function(node) {
|
||||
if (isParenthesised(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
|
||||
if (hasExcessParens(node.test) && precedence(node.test) >= precedence({type: "LogicalExpression", operator: "||"})) {
|
||||
report(node.test);
|
||||
}
|
||||
if (isParenthesised(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
|
||||
if (hasExcessParens(node.consequent) && precedence(node.consequent) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(node.consequent);
|
||||
}
|
||||
if (isParenthesised(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
|
||||
if (hasExcessParens(node.alternate) && precedence(node.alternate) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(node.alternate);
|
||||
}
|
||||
},
|
||||
"DoWhileStatement": function(node) {
|
||||
if (isParenthesisedTwice(node.test)) {
|
||||
if (hasDoubleExcessParens(node.test)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
"ExpressionStatement": function(node) {
|
||||
var firstToken;
|
||||
if (isParenthesised(node.expression)) {
|
||||
if (hasExcessParens(node.expression) && node.expression.type !== "CallExpression") {
|
||||
firstToken = context.getFirstToken(node.expression);
|
||||
if (firstToken.value !== "function" && firstToken.value !== "{") {
|
||||
// Pure object literals ({}) do not need parentheses but
|
||||
// member expressions do ({}.toString())
|
||||
if (firstToken.value !== "{" || node.expression.type === "ObjectExpression") {
|
||||
report(node.expression);
|
||||
}
|
||||
}
|
||||
},
|
||||
"ForInStatement": function(node) {
|
||||
if (isParenthesised(node.right)) {
|
||||
if (hasExcessParens(node.right)) {
|
||||
report(node.right);
|
||||
}
|
||||
},
|
||||
"ForOfStatement": function(node) {
|
||||
if (isParenthesised(node.right)) {
|
||||
if (hasExcessParens(node.right)) {
|
||||
report(node.right);
|
||||
}
|
||||
},
|
||||
"ForStatement": function(node) {
|
||||
if (node.init && isParenthesised(node.init)) {
|
||||
if (node.init && hasExcessParens(node.init)) {
|
||||
report(node.init);
|
||||
}
|
||||
|
||||
if (node.test && isParenthesised(node.test)) {
|
||||
if (node.test && hasExcessParens(node.test)) {
|
||||
report(node.test);
|
||||
}
|
||||
|
||||
if (node.update && isParenthesised(node.update)) {
|
||||
if (node.update && hasExcessParens(node.update)) {
|
||||
report(node.update);
|
||||
}
|
||||
},
|
||||
"IfStatement": function(node) {
|
||||
if (isParenthesisedTwice(node.test)) {
|
||||
if (hasDoubleExcessParens(node.test)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
"LogicalExpression": dryBinaryLogical,
|
||||
"MemberExpression": function(node) {
|
||||
if (
|
||||
isParenthesised(node.object) &&
|
||||
hasExcessParens(node.object) &&
|
||||
precedence(node.object) >= precedence(node) &&
|
||||
(
|
||||
node.computed ||
|
||||
@ -235,18 +295,21 @@ module.exports = function(context) {
|
||||
) {
|
||||
report(node.object);
|
||||
}
|
||||
if (node.computed && hasExcessParens(node.property)) {
|
||||
report(node.property);
|
||||
}
|
||||
},
|
||||
"NewExpression": dryCallNew,
|
||||
"ObjectExpression": function(node) {
|
||||
[].forEach.call(node.properties, function(e) {
|
||||
var v = e.value;
|
||||
if (v && isParenthesised(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
|
||||
if (v && hasExcessParens(v) && precedence(v) >= precedence({type: "AssignmentExpression"})) {
|
||||
report(v);
|
||||
}
|
||||
});
|
||||
},
|
||||
"ReturnStatement": function(node) {
|
||||
if (node.argument && isParenthesised(node.argument) &&
|
||||
if (node.argument && hasExcessParens(node.argument) &&
|
||||
// RegExp literal is allowed to have parens (#1589)
|
||||
!(node.argument.type === "Literal" && node.argument.regex)) {
|
||||
report(node.argument);
|
||||
@ -254,30 +317,30 @@ module.exports = function(context) {
|
||||
},
|
||||
"SequenceExpression": function(node) {
|
||||
[].forEach.call(node.expressions, function(e) {
|
||||
if (isParenthesised(e) && precedence(e) >= precedence(node)) {
|
||||
if (hasExcessParens(e) && precedence(e) >= precedence(node)) {
|
||||
report(e);
|
||||
}
|
||||
});
|
||||
},
|
||||
"SwitchCase": function(node) {
|
||||
if (node.test && isParenthesised(node.test)) {
|
||||
if (node.test && hasExcessParens(node.test)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
"SwitchStatement": function(node) {
|
||||
if (isParenthesisedTwice(node.discriminant)) {
|
||||
if (hasDoubleExcessParens(node.discriminant)) {
|
||||
report(node.discriminant);
|
||||
}
|
||||
},
|
||||
"ThrowStatement": function(node) {
|
||||
if (isParenthesised(node.argument)) {
|
||||
if (hasExcessParens(node.argument)) {
|
||||
report(node.argument);
|
||||
}
|
||||
},
|
||||
"UnaryExpression": dryUnaryUpdate,
|
||||
"UpdateExpression": dryUnaryUpdate,
|
||||
"VariableDeclarator": function(node) {
|
||||
if (node.init && isParenthesised(node.init) &&
|
||||
if (node.init && hasExcessParens(node.init) &&
|
||||
precedence(node.init) >= precedence({type: "AssignmentExpression"}) &&
|
||||
// RegExp literal is allowed to have parens (#1589)
|
||||
!(node.init.type === "Literal" && node.init.regex)) {
|
||||
@ -285,15 +348,21 @@ module.exports = function(context) {
|
||||
}
|
||||
},
|
||||
"WhileStatement": function(node) {
|
||||
if (isParenthesisedTwice(node.test)) {
|
||||
if (hasDoubleExcessParens(node.test)) {
|
||||
report(node.test);
|
||||
}
|
||||
},
|
||||
"WithStatement": function(node) {
|
||||
if (isParenthesisedTwice(node.object)) {
|
||||
if (hasDoubleExcessParens(node.object)) {
|
||||
report(node.object);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["all", "functions"]
|
||||
}
|
||||
];
|
||||
|
@ -11,11 +11,60 @@
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
return {
|
||||
/**
|
||||
* Reports an unnecessary semicolon error.
|
||||
* @param {Node|Token} nodeOrToken - A node or a token to be reported.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(nodeOrToken) {
|
||||
context.report(nodeOrToken, "Unnecessary semicolon.");
|
||||
}
|
||||
|
||||
"EmptyStatement": function(node) {
|
||||
context.report(node, "Unnecessary semicolon.");
|
||||
/**
|
||||
* Checks for a part of a class body.
|
||||
* This checks tokens from a specified token to a next MethodDefinition or the end of class body.
|
||||
*
|
||||
* @param {Token} firstToken - The first token to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForPartOfClassBody(firstToken) {
|
||||
for (var token = firstToken;
|
||||
token.type === "Punctuator" && token.value !== "}";
|
||||
token = context.getTokenAfter(token)
|
||||
) {
|
||||
if (token.value === ";") {
|
||||
report(token);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
/**
|
||||
* Reports this empty statement.
|
||||
* @param {Node} node - A EmptyStatement node to be reported.
|
||||
* @returns {void}
|
||||
*/
|
||||
"EmptyStatement": report,
|
||||
|
||||
/**
|
||||
* Checks tokens from the head of this class body to the first MethodDefinition or the end of this class body.
|
||||
* @param {Node} node - A ClassBody node to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
"ClassBody": function(node) {
|
||||
checkForPartOfClassBody(context.getFirstToken(node, 1)); // 0 is `{`.
|
||||
},
|
||||
|
||||
/**
|
||||
* Checks tokens from this MethodDefinition to the next MethodDefinition or the end of this class body.
|
||||
* @param {Node} node - A MethodDefinition node of the start point.
|
||||
* @returns {void}
|
||||
*/
|
||||
"MethodDefinition": function(node) {
|
||||
checkForPartOfClassBody(context.getTokenAfter(node));
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -82,3 +82,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -93,3 +93,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -26,3 +26,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -79,3 +79,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -72,3 +72,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -45,3 +45,5 @@ module.exports = function(context) {
|
||||
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -70,3 +70,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["functions", "both"]
|
||||
}
|
||||
];
|
||||
|
@ -49,3 +49,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -12,7 +12,8 @@
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u2028\u2029\u202f\u205f\u3000]+/mg;
|
||||
var irregularWhitespace = /[\u0085\u00A0\ufeff\f\v\u00a0\u1680\u180e\u2000\u2001\u2002\u2003\u2004\u2005\u2006\u2007\u2008\u2009\u200a\u200b\u202f\u205f\u3000]+/mg,
|
||||
irregularLineTerminators = /[\u2028\u2029]/mg;
|
||||
|
||||
// Module store of errors that we have found
|
||||
var errors = [];
|
||||
@ -30,7 +31,7 @@ module.exports = function(context) {
|
||||
errors = errors.filter(function (error) {
|
||||
var errorLoc = error[1];
|
||||
if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
|
||||
if (errorLoc.column >= locStart.column && errorLoc.column <= locEnd.column) {
|
||||
if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
|
||||
return false;
|
||||
}
|
||||
}
|
||||
@ -39,7 +40,7 @@ module.exports = function(context) {
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks nodes for errors that we are choosing to ignore and calls the relevent methods to remove the errors
|
||||
* Checks nodes for errors that we are choosing to ignore and calls the relevant methods to remove the errors
|
||||
* @param {ASTNode} node to check for matching errors.
|
||||
* @returns {void}
|
||||
* @private
|
||||
@ -47,12 +48,65 @@ module.exports = function(context) {
|
||||
function removeInvalidNodeErrors(node) {
|
||||
if (typeof node.value === "string") {
|
||||
// If we have irregular characters remove them from the errors list
|
||||
if (node.value.match(irregularWhitespace)) {
|
||||
if (node.raw.match(irregularWhitespace) || node.raw.match(irregularLineTerminators)) {
|
||||
removeStringError(node);
|
||||
}
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the program source for irregular whitespace
|
||||
* @param {ASTNode} node The program node
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkForIrregularWhitespace(node) {
|
||||
var sourceLines = context.getSourceLines();
|
||||
|
||||
sourceLines.forEach(function (sourceLine, lineIndex) {
|
||||
var lineNumber = lineIndex + 1,
|
||||
location,
|
||||
match;
|
||||
|
||||
while ((match = irregularWhitespace.exec(sourceLine)) !== null) {
|
||||
location = {
|
||||
line: lineNumber,
|
||||
column: match.index
|
||||
};
|
||||
|
||||
errors.push([node, location, "Irregular whitespace not allowed"]);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the program source for irregular line terminators
|
||||
* @param {ASTNode} node The program node
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function checkForIrregularLineTerminators(node) {
|
||||
var source = context.getSource(),
|
||||
sourceLines = context.getSourceLines(),
|
||||
linebreaks = source.match(/\r\n|\r|\n|\u2028|\u2029/g),
|
||||
lastLineIndex = -1,
|
||||
lineIndex,
|
||||
location,
|
||||
match;
|
||||
|
||||
while ((match = irregularLineTerminators.exec(source)) !== null) {
|
||||
lineIndex = linebreaks.indexOf(match[0], lastLineIndex + 1) || 0;
|
||||
|
||||
location = {
|
||||
line: lineIndex + 1,
|
||||
column: sourceLines[lineIndex].length
|
||||
};
|
||||
|
||||
errors.push([node, location, "Irregular whitespace not allowed"]);
|
||||
lastLineIndex = lineIndex;
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
"Program": function (node) {
|
||||
/**
|
||||
@ -61,26 +115,13 @@ module.exports = function(context) {
|
||||
* When writing this code also evaluating per node was missing out connecting tokens in some cases
|
||||
* We can later filter the errors when they are found to be not an issue in nodes we don't care about
|
||||
*/
|
||||
var sourceLines = context.getSourceLines();
|
||||
|
||||
sourceLines.forEach(function (sourceLine, lineIndex) {
|
||||
var location,
|
||||
match = irregularWhitespace.exec(sourceLine);
|
||||
|
||||
if (match !== null) {
|
||||
location = {
|
||||
line: lineIndex + 1,
|
||||
column: match.index
|
||||
};
|
||||
|
||||
errors.push([node, location, "Irregular whitespace not allowed"]);
|
||||
}
|
||||
});
|
||||
checkForIrregularWhitespace(node);
|
||||
checkForIrregularLineTerminators(node);
|
||||
},
|
||||
|
||||
"Identifier": removeInvalidNodeErrors,
|
||||
"Literal": removeInvalidNodeErrors,
|
||||
"Statement": removeInvalidNodeErrors,
|
||||
"Expression": removeInvalidNodeErrors,
|
||||
"Program:exit": function () {
|
||||
|
||||
// If we have any errors remaining report on them
|
||||
@ -90,3 +131,5 @@ module.exports = function(context) {
|
||||
}
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -24,3 +24,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -60,3 +60,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -40,3 +40,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -1,6 +1,8 @@
|
||||
/**
|
||||
* @fileoverview Rule to flag blocks with no reason to exist
|
||||
* @author Brandon Mills
|
||||
* @copyright 2015 Roberto Vidal. All rights reserved.
|
||||
* @copyright 2014 Brandon Mills. All rights reserved.
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
@ -11,15 +13,94 @@
|
||||
|
||||
module.exports = function(context) {
|
||||
|
||||
return {
|
||||
"BlockStatement": function (node) {
|
||||
// Check for any occurrence of BlockStatement > BlockStatement or
|
||||
// Program > BlockStatement
|
||||
var parent = context.getAncestors().pop();
|
||||
if (parent.type === "BlockStatement" || parent.type === "Program") {
|
||||
context.report(node, "Block is nested inside another block.");
|
||||
// A stack of lone blocks to be checked for block-level bindings
|
||||
var loneBlocks = [],
|
||||
ruleDef;
|
||||
|
||||
/**
|
||||
* Reports a node as invalid.
|
||||
* @param {ASTNode} node - The node to be reported.
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(node) {
|
||||
var parent = context.getAncestors().pop();
|
||||
context.report(node, parent.type === "Program" ?
|
||||
"Block is redundant." :
|
||||
"Nested block is redundant."
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks for any ocurrence of BlockStatement > BlockStatement or Program > BlockStatement
|
||||
* @returns {boolean} True if the current node is a lone block.
|
||||
*/
|
||||
function isLoneBlock() {
|
||||
var parent = context.getAncestors().pop();
|
||||
return parent.type === "BlockStatement" || parent.type === "Program";
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks the enclosing block of the current node for block-level bindings,
|
||||
* and "marks it" as valid if any.
|
||||
* @returns {void}
|
||||
*/
|
||||
function markLoneBlock() {
|
||||
if (loneBlocks.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
var block = context.getAncestors().pop();
|
||||
|
||||
if (loneBlocks[loneBlocks.length - 1] === block) {
|
||||
loneBlocks.pop();
|
||||
}
|
||||
}
|
||||
|
||||
// Default rule definition: report all lone blocks
|
||||
ruleDef = {
|
||||
BlockStatement: function(node) {
|
||||
if (isLoneBlock(node)) {
|
||||
report(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
// ES6: report blocks without block-level bindings
|
||||
if (context.ecmaFeatures.blockBindings || context.ecmaFeatures.classes) {
|
||||
ruleDef = {
|
||||
"BlockStatement": function(node) {
|
||||
if (isLoneBlock(node)) {
|
||||
loneBlocks.push(node);
|
||||
}
|
||||
},
|
||||
"BlockStatement:exit": function(node) {
|
||||
if (loneBlocks.length > 0 && loneBlocks[loneBlocks.length - 1] === node) {
|
||||
loneBlocks.pop();
|
||||
report(node);
|
||||
}
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (context.ecmaFeatures.blockBindings) {
|
||||
ruleDef.VariableDeclaration = function(node) {
|
||||
if (node.kind === "let" || node.kind === "const") {
|
||||
markLoneBlock(node);
|
||||
}
|
||||
};
|
||||
|
||||
ruleDef.FunctionDeclaration = function(node) {
|
||||
if (context.getScope().isStrict) {
|
||||
markLoneBlock(node);
|
||||
}
|
||||
};
|
||||
}
|
||||
|
||||
if (context.ecmaFeatures.classes) {
|
||||
ruleDef.ClassDeclaration = markLoneBlock;
|
||||
}
|
||||
|
||||
return ruleDef;
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -26,3 +26,5 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -11,23 +11,6 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = function(context) {
|
||||
var loopNodeTypes = [
|
||||
"ForStatement",
|
||||
"WhileStatement",
|
||||
"ForInStatement",
|
||||
"ForOfStatement",
|
||||
"DoWhileStatement"
|
||||
];
|
||||
|
||||
/**
|
||||
* Checks if the given node is a loop.
|
||||
* @param {ASTNode} node The AST node to check.
|
||||
* @returns {boolean} Whether or not the node is a loop.
|
||||
*/
|
||||
function isLoop(node) {
|
||||
return loopNodeTypes.indexOf(node.type) > -1;
|
||||
}
|
||||
|
||||
/**
|
||||
* Reports if the given node has an ancestor node which is a loop.
|
||||
* @param {ASTNode} node The AST node to check.
|
||||
@ -36,7 +19,31 @@ module.exports = function(context) {
|
||||
function checkForLoops(node) {
|
||||
var ancestors = context.getAncestors();
|
||||
|
||||
if (ancestors.some(isLoop)) {
|
||||
/**
|
||||
* Checks if the given node is a loop and current context is in the loop.
|
||||
* @param {ASTNode} ancestor The AST node to check.
|
||||
* @param {number} index The index of ancestor in ancestors.
|
||||
* @returns {boolean} Whether or not the node is a loop and current context is in the loop.
|
||||
*/
|
||||
function isInLoop(ancestor, index) {
|
||||
switch (ancestor.type) {
|
||||
case "ForStatement":
|
||||
return ancestor.init !== ancestors[index + 1];
|
||||
|
||||
case "ForInStatement":
|
||||
case "ForOfStatement":
|
||||
return ancestor.right !== ancestors[index + 1];
|
||||
|
||||
case "WhileStatement":
|
||||
case "DoWhileStatement":
|
||||
return true;
|
||||
|
||||
default:
|
||||
return false;
|
||||
}
|
||||
}
|
||||
|
||||
if (ancestors.some(isInLoop)) {
|
||||
context.report(node, "Don't make functions within a loop");
|
||||
}
|
||||
}
|
||||
@ -47,3 +54,5 @@ module.exports = function(context) {
|
||||
"FunctionDeclaration": checkForLoops
|
||||
};
|
||||
};
|
||||
|
||||
module.exports.schema = [];
|
||||
|
@ -157,3 +157,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"type": "boolean"
|
||||
}
|
||||
];
|
||||
|
@ -66,3 +66,9 @@ module.exports = function(context) {
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
module.exports.schema = [
|
||||
{
|
||||
"enum": ["smart-tabs", true, false]
|
||||
}
|
||||
];
|
||||
|
Some files were not shown because too many files have changed in this diff Show More
Loading…
x
Reference in New Issue
Block a user