mirror of
https://github.com/nodejs/node.git
synced 2026-10-14 10:26:26 -04:00
tools: update to ESLint 4.8.0
PR-URL: https://github.com/nodejs/node/pull/16199 Reviewed-By: James M Snell <jasnell@gmail.com> Reviewed-By: Colin Ihrig <cjihrig@gmail.com> Reviewed-By: Yuta Hiroto <hello@about-hiroppy.com> Reviewed-By: Ruben Bridgewater <ruben@bridgewater.de>
This commit is contained in:
476 files changed
+5652
-23885
No files matched your search
+17
-18
@@ -9,11 +9,11 @@
|
||||
|
||||
# ESLint
|
||||
|
||||
[Website](http://eslint.org) |
|
||||
[Configuring](http://eslint.org/docs/user-guide/configuring) |
|
||||
[Rules](http://eslint.org/docs/rules/) |
|
||||
[Contributing](http://eslint.org/docs/developer-guide/contributing) |
|
||||
[Reporting Bugs](http://eslint.org/docs/developer-guide/contributing/reporting-bugs) |
|
||||
[Website](https://eslint.org) |
|
||||
[Configuring](https://eslint.org/docs/user-guide/configuring) |
|
||||
[Rules](https://eslint.org/docs/rules/) |
|
||||
[Contributing](https://eslint.org/docs/developer-guide/contributing) |
|
||||
[Reporting Bugs](https://eslint.org/docs/developer-guide/contributing/reporting-bugs) |
|
||||
[Code of Conduct](https://js.foundation/community/code-of-conduct) |
|
||||
[Twitter](https://twitter.com/geteslint) |
|
||||
[Mailing List](https://groups.google.com/group/eslint) |
|
||||
@@ -88,17 +88,17 @@ After running `eslint --init`, you'll have a `.eslintrc` file in your directory.
|
||||
}
|
||||
```
|
||||
|
||||
The names `"semi"` and `"quotes"` are the names of [rules](http://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values:
|
||||
The names `"semi"` and `"quotes"` are the names of [rules](https://eslint.org/docs/rules) in ESLint. The first value is the error level of the rule and can be one of these values:
|
||||
|
||||
* `"off"` or `0` - turn the rule off
|
||||
* `"warn"` or `1` - turn the rule on as a warning (doesn't affect exit code)
|
||||
* `"error"` or `2` - turn the rule on as an error (exit code will be 1)
|
||||
|
||||
The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](http://eslint.org/docs/user-guide/configuring)).
|
||||
The three error levels allow you fine-grained control over how ESLint applies rules (for more configuration options and details, see the [configuration docs](https://eslint.org/docs/user-guide/configuring)).
|
||||
|
||||
## Sponsors
|
||||
|
||||
* Site search ([eslint.org](http://eslint.org)) is sponsored by [Algolia](https://www.algolia.com)
|
||||
* Site search ([eslint.org](https://eslint.org)) is sponsored by [Algolia](https://www.algolia.com)
|
||||
|
||||
## Team
|
||||
|
||||
@@ -145,10 +145,10 @@ ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/comm
|
||||
|
||||
Before filing an issue, please be sure to read the guidelines for what you're reporting:
|
||||
|
||||
* [Bug Report](http://eslint.org/docs/developer-guide/contributing/reporting-bugs)
|
||||
* [Propose a New Rule](http://eslint.org/docs/developer-guide/contributing/new-rules)
|
||||
* [Proposing a Rule Change](http://eslint.org/docs/developer-guide/contributing/rule-changes)
|
||||
* [Request a Change](http://eslint.org/docs/developer-guide/contributing/changes)
|
||||
* [Bug Report](https://eslint.org/docs/developer-guide/contributing/reporting-bugs)
|
||||
* [Propose a New Rule](https://eslint.org/docs/developer-guide/contributing/new-rules)
|
||||
* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes)
|
||||
* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes)
|
||||
|
||||
## Semantic Versioning Policy
|
||||
|
||||
@@ -171,7 +171,6 @@ ESLint follows [semantic versioning](http://semver.org). However, due to the nat
|
||||
* Major release (likely to break your lint build)
|
||||
* `eslint:recommended` is updated.
|
||||
* A new option to an existing rule that results in ESLint reporting more errors by default.
|
||||
* An existing rule is removed.
|
||||
* An existing formatter is removed.
|
||||
* Part of the public API is removed or changed in an incompatible way.
|
||||
|
||||
@@ -195,11 +194,11 @@ Despite being slower, we believe that ESLint is fast enough to replace JSHint wi
|
||||
|
||||
### I heard ESLint is going to replace JSCS?
|
||||
|
||||
Yes. Since we are solving the same problems, ESLint and JSCS teams have decided to join forces and work together in the development of ESLint instead of competing with each other. You can read more about this in both [ESLint](http://eslint.org/blog/2016/04/welcoming-jscs-to-eslint) and [JSCS](https://medium.com/@markelog/jscs-end-of-the-line-bc9bf0b3fdb2#.u76sx334n) announcements.
|
||||
Yes. Since we are solving the same problems, ESLint and JSCS teams have decided to join forces and work together in the development of ESLint instead of competing with each other. You can read more about this in both [ESLint](https://eslint.org/blog/2016/04/welcoming-jscs-to-eslint) and [JSCS](https://medium.com/@markelog/jscs-end-of-the-line-bc9bf0b3fdb2#.u76sx334n) announcements.
|
||||
|
||||
### So, should I stop using JSCS and start using ESLint?
|
||||
|
||||
Maybe, depending on how much you need it. [JSCS has reached end of life](http://eslint.org/blog/2016/07/jscs-end-of-life), but if it is working for you then there is no reason to move yet. We are still working to smooth the transition. You can see our progress [here](https://github.com/eslint/eslint/milestones/JSCS%20Compatibility). We’ll announce when all of the changes necessary to support JSCS users in ESLint are complete and will start encouraging JSCS users to switch to ESLint at that time.
|
||||
Maybe, depending on how much you need it. [JSCS has reached end of life](https://eslint.org/blog/2016/07/jscs-end-of-life), but if it is working for you then there is no reason to move yet. We are still working to smooth the transition. You can see our progress [here](https://github.com/eslint/eslint/milestones/JSCS%20Compatibility). We’ll announce when all of the changes necessary to support JSCS users in ESLint are complete and will start encouraging JSCS users to switch to ESLint at that time.
|
||||
|
||||
If you are having issues with JSCS, you can try to move to ESLint. We are focusing our time and energy on JSCS compatibility issues.
|
||||
|
||||
@@ -210,17 +209,17 @@ ESLint does both traditional linting (looking for problematic patterns) and styl
|
||||
|
||||
### 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.
|
||||
Yes, ESLint natively supports parsing JSX syntax (this must be enabled in [configuration](https://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 6 support?
|
||||
|
||||
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 syntax and global variables through [configuration](http://eslint.org/docs/user-guide/configuring).
|
||||
ESLint has full support for ECMAScript 6. By default, this support is off. You can enable ECMAScript 6 syntax and global variables through [configuration](https://eslint.org/docs/user-guide/configuring).
|
||||
|
||||
### What about 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.
|
||||
|
||||
Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](http://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature.
|
||||
Once a language feature has been adopted into the ECMAScript standard (stage 4 according to the [TC39 process](https://tc39.github.io/process-document/)), we will accept issues and pull requests related to the new feature, subject to our [contributing guidelines](https://eslint.org/docs/developer-guide/contributing). Until then, please use the appropriate parser and plugin(s) for your experimental feature.
|
||||
|
||||
### Where to ask for help?
|
||||
|
||||
|
||||
@@ -43,10 +43,13 @@ process.once("uncaughtException", err => {
|
||||
|
||||
if (typeof err.messageTemplate === "string" && err.messageTemplate.length > 0) {
|
||||
const template = lodash.template(fs.readFileSync(path.resolve(__dirname, `../messages/${err.messageTemplate}.txt`), "utf-8"));
|
||||
const pkg = require("../package.json");
|
||||
|
||||
console.error("\nOops! Something went wrong! :(");
|
||||
console.error(`\n${template(err.messageData || {})}`);
|
||||
console.error(`\nESLint: ${pkg.version}.\n${template(err.messageData || {})}`);
|
||||
} else {
|
||||
|
||||
console.error(err.message);
|
||||
console.error(err.stack);
|
||||
}
|
||||
|
||||
|
||||
@@ -10,12 +10,12 @@
|
||||
],
|
||||
"deprecated": {
|
||||
"name": "Deprecated",
|
||||
"description": "These rules have been deprecated and replaced by newer rules:",
|
||||
"description": "These rules have been deprecated in accordance with the [deprecation policy](/docs/user-guide/rule-deprecation), and replaced by newer rules:",
|
||||
"rules": []
|
||||
},
|
||||
"removed": {
|
||||
"name": "Removed",
|
||||
"description": "These rules from older versions of ESLint have been replaced by newer rules:",
|
||||
"description": "These rules from older versions of ESLint (before the [deprecation policy](/docs/user-guide/rule-deprecation) existed) have been replaced by newer rules:",
|
||||
"rules": [
|
||||
{ "removed": "generator-star", "replacedBy": ["generator-star-spacing"] },
|
||||
{ "removed": "global-strict", "replacedBy": ["strict"] },
|
||||
|
||||
@@ -12,7 +12,9 @@ const baseConfigProperties = {
|
||||
parserOptions: { type: "object" },
|
||||
plugins: { type: "array" },
|
||||
rules: { type: "object" },
|
||||
settings: { type: "object" }
|
||||
settings: { type: "object" },
|
||||
|
||||
ecmaFeatures: { type: "object" } // deprecated; logs a warning when used
|
||||
};
|
||||
|
||||
const overrideProperties = Object.assign(
|
||||
|
||||
@@ -23,5 +23,6 @@ module.exports = {
|
||||
cacheLocation: "",
|
||||
cacheFile: ".eslintcache",
|
||||
fix: false,
|
||||
allowInlineConfig: true
|
||||
allowInlineConfig: true,
|
||||
reportUnusedDisableDirectives: false
|
||||
};
|
||||
@@ -6,13 +6,10 @@
|
||||
|
||||
"use strict";
|
||||
|
||||
/* eslint sort-keys: ["error", "asc"], quote-props: ["error", "consistent"] */
|
||||
/* eslint-disable sort-keys */
|
||||
/* eslint sort-keys: ["error", "asc"] */
|
||||
|
||||
module.exports = {
|
||||
rules: {
|
||||
|
||||
/* eslint-enable sort-keys */
|
||||
"accessor-pairs": "off",
|
||||
"array-bracket-newline": "off",
|
||||
"array-bracket-spacing": "off",
|
||||
@@ -25,28 +22,29 @@ module.exports = {
|
||||
"block-spacing": "off",
|
||||
"brace-style": "off",
|
||||
"callback-return": "off",
|
||||
"camelcase": "off",
|
||||
camelcase: "off",
|
||||
"capitalized-comments": "off",
|
||||
"class-methods-use-this": "off",
|
||||
"comma-dangle": "off",
|
||||
"comma-spacing": "off",
|
||||
"comma-style": "off",
|
||||
"complexity": "off",
|
||||
complexity: "off",
|
||||
"computed-property-spacing": "off",
|
||||
"consistent-return": "off",
|
||||
"consistent-this": "off",
|
||||
"constructor-super": "error",
|
||||
"curly": "off",
|
||||
curly: "off",
|
||||
"default-case": "off",
|
||||
"dot-location": "off",
|
||||
"dot-notation": "off",
|
||||
"eol-last": "off",
|
||||
"eqeqeq": "off",
|
||||
eqeqeq: "off",
|
||||
"for-direction": "off",
|
||||
"func-call-spacing": "off",
|
||||
"func-name-matching": "off",
|
||||
"func-names": "off",
|
||||
"func-style": "off",
|
||||
"function-paren-newline": "off",
|
||||
"generator-star-spacing": "off",
|
||||
"getter-return": "off",
|
||||
"global-require": "off",
|
||||
@@ -55,7 +53,7 @@ module.exports = {
|
||||
"id-blacklist": "off",
|
||||
"id-length": "off",
|
||||
"id-match": "off",
|
||||
"indent": "off",
|
||||
indent: "off",
|
||||
"indent-legacy": "off",
|
||||
"init-declarations": "off",
|
||||
"jsx-quotes": "off",
|
||||
@@ -234,13 +232,13 @@ module.exports = {
|
||||
"prefer-spread": "off",
|
||||
"prefer-template": "off",
|
||||
"quote-props": "off",
|
||||
"quotes": "off",
|
||||
"radix": "off",
|
||||
quotes: "off",
|
||||
radix: "off",
|
||||
"require-await": "off",
|
||||
"require-jsdoc": "off",
|
||||
"require-yield": "error",
|
||||
"rest-spread-spacing": "off",
|
||||
"semi": "off",
|
||||
semi: "off",
|
||||
"semi-spacing": "off",
|
||||
"semi-style": "off",
|
||||
"sort-imports": "off",
|
||||
@@ -252,7 +250,7 @@ module.exports = {
|
||||
"space-infix-ops": "off",
|
||||
"space-unary-ops": "off",
|
||||
"spaced-comment": "off",
|
||||
"strict": "off",
|
||||
strict: "off",
|
||||
"switch-colon-spacing": "off",
|
||||
"symbol-description": "off",
|
||||
"template-curly-spacing": "off",
|
||||
@@ -265,6 +263,6 @@ module.exports = {
|
||||
"wrap-iife": "off",
|
||||
"wrap-regex": "off",
|
||||
"yield-star-spacing": "off",
|
||||
"yoda": "off"
|
||||
yoda: "off"
|
||||
}
|
||||
};
|
||||
@@ -626,6 +626,9 @@ module.exports = {
|
||||
// // setup...
|
||||
// return function foo() { ... };
|
||||
// })();
|
||||
// obj.foo = (() =>
|
||||
// function foo() { ... }
|
||||
// )();
|
||||
case "ReturnStatement": {
|
||||
const func = getUpperFunction(parent);
|
||||
|
||||
@@ -635,6 +638,12 @@ module.exports = {
|
||||
node = func.parent;
|
||||
break;
|
||||
}
|
||||
case "ArrowFunctionExpression":
|
||||
if (node !== parent.body || !isCallee(parent)) {
|
||||
return true;
|
||||
}
|
||||
node = parent.parent;
|
||||
break;
|
||||
|
||||
// e.g.
|
||||
// var obj = { foo() { ... } };
|
||||
@@ -655,16 +664,15 @@ module.exports = {
|
||||
// [Foo = function() { ... }] = a;
|
||||
case "AssignmentExpression":
|
||||
case "AssignmentPattern":
|
||||
if (parent.right === node) {
|
||||
if (parent.left.type === "MemberExpression") {
|
||||
return false;
|
||||
}
|
||||
if (isAnonymous &&
|
||||
parent.left.type === "Identifier" &&
|
||||
startsWithUpperCase(parent.left.name)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
if (parent.left.type === "MemberExpression") {
|
||||
return false;
|
||||
}
|
||||
if (
|
||||
isAnonymous &&
|
||||
parent.left.type === "Identifier" &&
|
||||
startsWithUpperCase(parent.left.name)
|
||||
) {
|
||||
return false;
|
||||
}
|
||||
return true;
|
||||
|
||||
@@ -809,19 +817,14 @@ module.exports = {
|
||||
return 17;
|
||||
|
||||
case "CallExpression":
|
||||
|
||||
// IIFE is allowed to have parens in any position (#655)
|
||||
if (node.callee.type === "FunctionExpression") {
|
||||
return -1;
|
||||
}
|
||||
return 18;
|
||||
|
||||
case "NewExpression":
|
||||
return 19;
|
||||
|
||||
// no default
|
||||
default:
|
||||
return 20;
|
||||
}
|
||||
return 20;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
+75
-127
@@ -56,6 +56,7 @@ const debug = require("debug")("eslint:cli-engine");
|
||||
* @property {string[]} plugins An array of plugins to load.
|
||||
* @property {Object<string,*>} rules An object of rules to use.
|
||||
* @property {string[]} rulePaths An array of directories to load custom rules from.
|
||||
* @property {boolean} reportUnusedDisableDirectives `true` adds reports for unused eslint-disable directives
|
||||
*/
|
||||
|
||||
/**
|
||||
@@ -137,20 +138,15 @@ function calculateStatsPerRun(results) {
|
||||
* @param {string} filename An optional string representing the texts filename.
|
||||
* @param {boolean|Function} fix Indicates if fixes should be processed.
|
||||
* @param {boolean} allowInlineConfig Allow/ignore comments that change config.
|
||||
* @param {boolean} reportUnusedDisableDirectives Allow/ignore comments that change config.
|
||||
* @param {Linter} linter Linter context
|
||||
* @returns {LintResult} The results for linting on this text.
|
||||
* @private
|
||||
*/
|
||||
function processText(text, configHelper, filename, fix, allowInlineConfig, linter) {
|
||||
|
||||
// clear all existing settings for a new file
|
||||
linter.reset();
|
||||
|
||||
function processText(text, configHelper, filename, fix, allowInlineConfig, reportUnusedDisableDirectives, linter) {
|
||||
let filePath,
|
||||
messages,
|
||||
fileExtension,
|
||||
processor,
|
||||
fixedResult;
|
||||
processor;
|
||||
|
||||
if (filename) {
|
||||
filePath = path.resolve(filename);
|
||||
@@ -174,51 +170,29 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, linte
|
||||
}
|
||||
}
|
||||
|
||||
if (processor) {
|
||||
debug("Using processor");
|
||||
const parsedBlocks = processor.preprocess(text, filename);
|
||||
const unprocessedMessages = [];
|
||||
const autofixingEnabled = typeof fix !== "undefined" && (!processor || processor.supportsAutofix);
|
||||
|
||||
parsedBlocks.forEach(block => {
|
||||
unprocessedMessages.push(linter.verify(block, config, {
|
||||
filename,
|
||||
allowInlineConfig
|
||||
}));
|
||||
});
|
||||
const fixedResult = linter.verifyAndFix(text, config, {
|
||||
filename,
|
||||
allowInlineConfig,
|
||||
reportUnusedDisableDirectives,
|
||||
fix: !!autofixingEnabled && fix,
|
||||
preprocess: processor && (rawText => processor.preprocess(rawText, filename)),
|
||||
postprocess: processor && (problemLists => processor.postprocess(problemLists, filename))
|
||||
});
|
||||
|
||||
// TODO(nzakas): Figure out how fixes might work for processors
|
||||
|
||||
messages = processor.postprocess(unprocessedMessages, filename);
|
||||
|
||||
} else {
|
||||
|
||||
if (fix) {
|
||||
fixedResult = linter.verifyAndFix(text, config, {
|
||||
filename,
|
||||
allowInlineConfig,
|
||||
fix
|
||||
});
|
||||
messages = fixedResult.messages;
|
||||
} else {
|
||||
messages = linter.verify(text, config, {
|
||||
filename,
|
||||
allowInlineConfig
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
const stats = calculateStatsPerFile(messages);
|
||||
const stats = calculateStatsPerFile(fixedResult.messages);
|
||||
|
||||
const result = {
|
||||
filePath: filename,
|
||||
messages,
|
||||
messages: fixedResult.messages,
|
||||
errorCount: stats.errorCount,
|
||||
warningCount: stats.warningCount,
|
||||
fixableErrorCount: stats.fixableErrorCount,
|
||||
fixableWarningCount: stats.fixableWarningCount
|
||||
};
|
||||
|
||||
if (fixedResult && fixedResult.fixed) {
|
||||
if (fixedResult.fixed) {
|
||||
result.output = fixedResult.output;
|
||||
}
|
||||
|
||||
@@ -242,7 +216,15 @@ function processText(text, configHelper, filename, fix, allowInlineConfig, linte
|
||||
function processFile(filename, configHelper, options, linter) {
|
||||
|
||||
const text = fs.readFileSync(path.resolve(filename), "utf8"),
|
||||
result = processText(text, configHelper, filename, options.fix, options.allowInlineConfig, linter);
|
||||
result = processText(
|
||||
text,
|
||||
configHelper,
|
||||
filename,
|
||||
options.fix,
|
||||
options.allowInlineConfig,
|
||||
options.reportUnusedDisableDirectives,
|
||||
linter
|
||||
);
|
||||
|
||||
return result;
|
||||
|
||||
@@ -372,6 +354,8 @@ function getCacheFile(cacheFile, cwd) {
|
||||
return resolvedCacheFile;
|
||||
}
|
||||
|
||||
const configHashCache = new WeakMap();
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -493,11 +477,9 @@ class CLIEngine {
|
||||
* @returns {Object} The results for all files that were linted.
|
||||
*/
|
||||
executeOnFiles(patterns) {
|
||||
const results = [],
|
||||
options = this.options,
|
||||
const options = this.options,
|
||||
fileCache = this._fileCache,
|
||||
configHelper = this.config;
|
||||
let prevConfig; // the previous configuration used
|
||||
const cacheFile = getCacheFile(this.options.cacheLocation || this.options.cacheFile, this.options.cwd);
|
||||
|
||||
if (!options.cache && fs.existsSync(cacheFile)) {
|
||||
@@ -512,42 +494,18 @@ class CLIEngine {
|
||||
function hashOfConfigFor(filename) {
|
||||
const config = configHelper.getConfig(filename);
|
||||
|
||||
if (!prevConfig) {
|
||||
prevConfig = {};
|
||||
if (!configHashCache.has(config)) {
|
||||
configHashCache.set(config, hash(`${pkg.version}_${stringify(config)}`));
|
||||
}
|
||||
|
||||
// reuse the previously hashed config if the config hasn't changed
|
||||
if (prevConfig.config !== config) {
|
||||
|
||||
/*
|
||||
* config changed so we need to calculate the hash of the config
|
||||
* and the hash of the plugins being used
|
||||
*/
|
||||
prevConfig.config = config;
|
||||
|
||||
const eslintVersion = pkg.version;
|
||||
|
||||
prevConfig.hash = hash(`${eslintVersion}_${stringify(config)}`);
|
||||
}
|
||||
|
||||
return prevConfig.hash;
|
||||
return configHashCache.get(config);
|
||||
}
|
||||
|
||||
/**
|
||||
* Executes the linter on a file defined by the `filename`. Skips
|
||||
* unsupported file extensions and any files that are already linted.
|
||||
* @param {string} filename The resolved filename of the file to be linted
|
||||
* @param {boolean} warnIgnored always warn when a file is ignored
|
||||
* @param {Linter} linter Linter context
|
||||
* @returns {void}
|
||||
*/
|
||||
function executeOnFile(filename, warnIgnored, linter) {
|
||||
let hashOfConfig,
|
||||
descriptor;
|
||||
|
||||
if (warnIgnored) {
|
||||
results.push(createIgnoreResult(filename, options.cwd));
|
||||
return;
|
||||
const startTime = Date.now();
|
||||
const fileList = globUtil.listFilesToProcess(this.resolveFileGlobPatterns(patterns), options);
|
||||
const results = fileList.map(fileInfo => {
|
||||
if (fileInfo.ignored) {
|
||||
return createIgnoreResult(fileInfo.filename, options.cwd);
|
||||
}
|
||||
|
||||
if (options.cache) {
|
||||
@@ -557,15 +515,12 @@ class CLIEngine {
|
||||
* with the metadata and the flag that determines if
|
||||
* the file has changed
|
||||
*/
|
||||
descriptor = fileCache.getFileDescriptor(filename);
|
||||
const meta = descriptor.meta || {};
|
||||
|
||||
hashOfConfig = hashOfConfigFor(filename);
|
||||
|
||||
const changed = descriptor.changed || meta.hashOfConfig !== hashOfConfig;
|
||||
const descriptor = fileCache.getFileDescriptor(fileInfo.filename);
|
||||
const hashOfConfig = hashOfConfigFor(fileInfo.filename);
|
||||
const changed = descriptor.changed || descriptor.meta.hashOfConfig !== hashOfConfig;
|
||||
|
||||
if (!changed) {
|
||||
debug(`Skipping file since hasn't changed: ${filename}`);
|
||||
debug(`Skipping file since hasn't changed: ${fileInfo.filename}`);
|
||||
|
||||
/*
|
||||
* Add the the cached results (always will be 0 error and
|
||||
@@ -573,63 +528,45 @@ class CLIEngine {
|
||||
* failed, in order to guarantee that next execution will
|
||||
* process those files as well.
|
||||
*/
|
||||
results.push(descriptor.meta.results);
|
||||
|
||||
// move to the next file
|
||||
return;
|
||||
return descriptor.meta.results;
|
||||
}
|
||||
}
|
||||
|
||||
debug(`Processing ${filename}`);
|
||||
debug(`Processing ${fileInfo.filename}`);
|
||||
|
||||
const res = processFile(filename, configHelper, options, linter);
|
||||
return processFile(fileInfo.filename, configHelper, options, this.linter);
|
||||
});
|
||||
|
||||
if (options.cache) {
|
||||
if (options.cache) {
|
||||
results.forEach(result => {
|
||||
if (result.messages.length) {
|
||||
|
||||
/*
|
||||
* if a file contains errors or warnings we don't want to
|
||||
* store the file in the cache so we can guarantee that
|
||||
* next execution will also operate on this file
|
||||
*/
|
||||
if (res.errorCount > 0 || res.warningCount > 0) {
|
||||
debug(`File has problems, skipping it: ${filename}`);
|
||||
|
||||
// remove the entry from the cache
|
||||
fileCache.removeEntry(filename);
|
||||
/*
|
||||
* if a file contains errors or warnings we don't want to
|
||||
* store the file in the cache so we can guarantee that
|
||||
* next execution will also operate on this file
|
||||
*/
|
||||
fileCache.removeEntry(result.filePath);
|
||||
} else {
|
||||
|
||||
/*
|
||||
* since the file passed we store the result here
|
||||
* TODO: check this as we might not need to store the
|
||||
* successful runs as it will always should be 0 errors and
|
||||
* 0 warnings.
|
||||
* TODO: it might not be necessary to store the results list in the cache,
|
||||
* since it should always be 0 errors/warnings
|
||||
*/
|
||||
descriptor.meta.hashOfConfig = hashOfConfig;
|
||||
descriptor.meta.results = res;
|
||||
const descriptor = fileCache.getFileDescriptor(result.filePath);
|
||||
|
||||
descriptor.meta.hashOfConfig = hashOfConfigFor(result.filePath);
|
||||
descriptor.meta.results = result;
|
||||
}
|
||||
}
|
||||
|
||||
results.push(res);
|
||||
}
|
||||
|
||||
const startTime = Date.now();
|
||||
|
||||
|
||||
patterns = this.resolveFileGlobPatterns(patterns);
|
||||
const fileList = globUtil.listFilesToProcess(patterns, options);
|
||||
|
||||
fileList.forEach(fileInfo => {
|
||||
executeOnFile(fileInfo.filename, fileInfo.ignored, this.linter);
|
||||
});
|
||||
|
||||
const stats = calculateStatsPerRun(results);
|
||||
|
||||
if (options.cache) {
|
||||
});
|
||||
|
||||
// persist the cache to disk
|
||||
fileCache.reconcile();
|
||||
}
|
||||
|
||||
const stats = calculateStatsPerRun(results);
|
||||
|
||||
debug(`Linting complete in: ${Date.now() - startTime}ms`);
|
||||
|
||||
return {
|
||||
@@ -665,7 +602,17 @@ class CLIEngine {
|
||||
results.push(createIgnoreResult(filename, options.cwd));
|
||||
}
|
||||
} else {
|
||||
results.push(processText(text, configHelper, filename, options.fix, options.allowInlineConfig, this.linter));
|
||||
results.push(
|
||||
processText(
|
||||
text,
|
||||
configHelper,
|
||||
filename,
|
||||
options.fix,
|
||||
options.allowInlineConfig,
|
||||
options.reportUnusedDisableDirectives,
|
||||
this.linter
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
const stats = calculateStatsPerRun(results);
|
||||
@@ -713,7 +660,6 @@ class CLIEngine {
|
||||
*/
|
||||
getFormatter(format) {
|
||||
|
||||
let formatterPath;
|
||||
|
||||
// default is stylish
|
||||
format = format || "stylish";
|
||||
@@ -724,6 +670,8 @@ class CLIEngine {
|
||||
// replace \ with / for Windows compatibility
|
||||
format = format.replace(/\\/g, "/");
|
||||
|
||||
let formatterPath;
|
||||
|
||||
// if there's a slash, then it's a file
|
||||
if (format.indexOf("/") > -1) {
|
||||
const cwd = this.options ? this.options.cwd : process.cwd();
|
||||
|
||||
@@ -64,7 +64,8 @@ function translateOptions(cliOptions) {
|
||||
cacheFile: cliOptions.cacheFile,
|
||||
cacheLocation: cliOptions.cacheLocation,
|
||||
fix: cliOptions.fix && (cliOptions.quiet ? quietFixPredicate : true),
|
||||
allowInlineConfig: cliOptions.inlineConfig
|
||||
allowInlineConfig: cliOptions.inlineConfig,
|
||||
reportUnusedDisableDirectives: cliOptions.reportUnusedDisableDirectives
|
||||
};
|
||||
}
|
||||
|
||||
|
||||
@@ -154,7 +154,8 @@ function forwardCurrentToHead(analyzer, node) {
|
||||
analyzer.emitter.emit(
|
||||
"onCodePathSegmentEnd",
|
||||
currentSegment,
|
||||
node);
|
||||
node
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -175,7 +176,8 @@ function forwardCurrentToHead(analyzer, node) {
|
||||
analyzer.emitter.emit(
|
||||
"onCodePathSegmentStart",
|
||||
headSegment,
|
||||
node);
|
||||
node
|
||||
);
|
||||
}
|
||||
}
|
||||
}
|
||||
@@ -202,7 +204,8 @@ function leaveFromCurrentSegment(analyzer, node) {
|
||||
analyzer.emitter.emit(
|
||||
"onCodePathSegmentEnd",
|
||||
currentSegment,
|
||||
node);
|
||||
node
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -369,7 +372,8 @@ function processCodePathToEnter(analyzer, node) {
|
||||
case "SwitchStatement":
|
||||
state.pushSwitchContext(
|
||||
node.cases.some(isCaseNode),
|
||||
astUtils.getLabel(node));
|
||||
astUtils.getLabel(node)
|
||||
);
|
||||
break;
|
||||
|
||||
case "TryStatement":
|
||||
|
||||
@@ -15,43 +15,6 @@ const debug = require("./debug-helpers");
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Replaces unused segments with the previous segments of each unused segment.
|
||||
*
|
||||
* @param {CodePathSegment[]} segments - An array of segments to replace.
|
||||
* @returns {CodePathSegment[]} The replaced array.
|
||||
*/
|
||||
function flattenUnusedSegments(segments) {
|
||||
const done = Object.create(null);
|
||||
const retv = [];
|
||||
|
||||
for (let i = 0; i < segments.length; ++i) {
|
||||
const segment = segments[i];
|
||||
|
||||
// Ignores duplicated.
|
||||
if (done[segment.id]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use previous segments if unused.
|
||||
if (!segment.internal.used) {
|
||||
for (let j = 0; j < segment.allPrevSegments.length; ++j) {
|
||||
const prevSegment = segment.allPrevSegments[j];
|
||||
|
||||
if (!done[prevSegment.id]) {
|
||||
done[prevSegment.id] = true;
|
||||
retv.push(prevSegment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done[segment.id] = true;
|
||||
retv.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return retv;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks whether or not a given segment is reachable.
|
||||
*
|
||||
@@ -163,8 +126,9 @@ class CodePathSegment {
|
||||
static newNext(id, allPrevSegments) {
|
||||
return new CodePathSegment(
|
||||
id,
|
||||
flattenUnusedSegments(allPrevSegments),
|
||||
allPrevSegments.some(isReachable));
|
||||
CodePathSegment.flattenUnusedSegments(allPrevSegments),
|
||||
allPrevSegments.some(isReachable)
|
||||
);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -175,7 +139,7 @@ class CodePathSegment {
|
||||
* @returns {CodePathSegment} The created segment.
|
||||
*/
|
||||
static newUnreachable(id, allPrevSegments) {
|
||||
const segment = new CodePathSegment(id, flattenUnusedSegments(allPrevSegments), false);
|
||||
const segment = new CodePathSegment(id, CodePathSegment.flattenUnusedSegments(allPrevSegments), false);
|
||||
|
||||
// In `if (a) return a; foo();` case, the unreachable segment preceded by
|
||||
// the return statement is not used but must not be remove.
|
||||
@@ -237,6 +201,43 @@ class CodePathSegment {
|
||||
static markPrevSegmentAsLooped(segment, prevSegment) {
|
||||
segment.internal.loopedPrevSegments.push(prevSegment);
|
||||
}
|
||||
|
||||
/**
|
||||
* Replaces unused segments with the previous segments of each unused segment.
|
||||
*
|
||||
* @param {CodePathSegment[]} segments - An array of segments to replace.
|
||||
* @returns {CodePathSegment[]} The replaced array.
|
||||
*/
|
||||
static flattenUnusedSegments(segments) {
|
||||
const done = Object.create(null);
|
||||
const retv = [];
|
||||
|
||||
for (let i = 0; i < segments.length; ++i) {
|
||||
const segment = segments[i];
|
||||
|
||||
// Ignores duplicated.
|
||||
if (done[segment.id]) {
|
||||
continue;
|
||||
}
|
||||
|
||||
// Use previous segments if unused.
|
||||
if (!segment.internal.used) {
|
||||
for (let j = 0; j < segment.allPrevSegments.length; ++j) {
|
||||
const prevSegment = segment.allPrevSegments[j];
|
||||
|
||||
if (!done[prevSegment.id]) {
|
||||
done[prevSegment.id] = true;
|
||||
retv.push(prevSegment);
|
||||
}
|
||||
}
|
||||
} else {
|
||||
done[segment.id] = true;
|
||||
retv.push(segment);
|
||||
}
|
||||
}
|
||||
|
||||
return retv;
|
||||
}
|
||||
}
|
||||
|
||||
module.exports = CodePathSegment;
|
||||
@@ -169,6 +169,9 @@ function removeConnection(prevSegments, nextSegments) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function makeLooped(state, fromSegments, toSegments) {
|
||||
fromSegments = CodePathSegment.flattenUnusedSegments(fromSegments);
|
||||
toSegments = CodePathSegment.flattenUnusedSegments(toSegments);
|
||||
|
||||
const end = Math.min(fromSegments.length, toSegments.length);
|
||||
|
||||
for (let i = 0; i < end; ++i) {
|
||||
@@ -843,21 +846,23 @@ class CodePathState {
|
||||
* This segment will leave at the end of this finally block.
|
||||
*/
|
||||
const segments = forkContext.makeNext(-1, -1);
|
||||
let j;
|
||||
|
||||
for (let i = 0; i < forkContext.count; ++i) {
|
||||
const prevSegsOfLeavingSegment = [headOfLeavingSegments[i]];
|
||||
|
||||
for (j = 0; j < returned.segmentsList.length; ++j) {
|
||||
for (let j = 0; j < returned.segmentsList.length; ++j) {
|
||||
prevSegsOfLeavingSegment.push(returned.segmentsList[j][i]);
|
||||
}
|
||||
for (j = 0; j < thrown.segmentsList.length; ++j) {
|
||||
for (let j = 0; j < thrown.segmentsList.length; ++j) {
|
||||
prevSegsOfLeavingSegment.push(thrown.segmentsList[j][i]);
|
||||
}
|
||||
|
||||
segments.push(CodePathSegment.newNext(
|
||||
this.idGenerator.next(),
|
||||
prevSegsOfLeavingSegment));
|
||||
segments.push(
|
||||
CodePathSegment.newNext(
|
||||
this.idGenerator.next(),
|
||||
prevSegsOfLeavingSegment
|
||||
)
|
||||
);
|
||||
}
|
||||
|
||||
this.pushForkContext(true);
|
||||
@@ -982,7 +987,6 @@ class CodePathState {
|
||||
|
||||
const forkContext = this.forkContext;
|
||||
const brokenForkContext = this.popBreakContext().brokenForkContext;
|
||||
let choiceContext;
|
||||
|
||||
// Creates a looped path.
|
||||
switch (context.type) {
|
||||
@@ -992,11 +996,12 @@ class CodePathState {
|
||||
makeLooped(
|
||||
this,
|
||||
forkContext.head,
|
||||
context.continueDestSegments);
|
||||
context.continueDestSegments
|
||||
);
|
||||
break;
|
||||
|
||||
case "DoWhileStatement": {
|
||||
choiceContext = this.popChoiceContext();
|
||||
const choiceContext = this.popChoiceContext();
|
||||
|
||||
if (!choiceContext.processed) {
|
||||
choiceContext.trueForkContext.add(forkContext.head);
|
||||
@@ -1013,7 +1018,8 @@ class CodePathState {
|
||||
makeLooped(
|
||||
this,
|
||||
segmentsList[i],
|
||||
context.entrySegments);
|
||||
context.entrySegments
|
||||
);
|
||||
}
|
||||
break;
|
||||
}
|
||||
@@ -1024,7 +1030,8 @@ class CodePathState {
|
||||
makeLooped(
|
||||
this,
|
||||
forkContext.head,
|
||||
context.leftSegments);
|
||||
context.leftSegments
|
||||
);
|
||||
break;
|
||||
|
||||
/* istanbul ignore next */
|
||||
@@ -1149,7 +1156,8 @@ class CodePathState {
|
||||
finalizeTestSegmentsOfFor(
|
||||
context,
|
||||
choiceContext,
|
||||
forkContext.head);
|
||||
forkContext.head
|
||||
);
|
||||
} else {
|
||||
context.endOfInitSegments = forkContext.head;
|
||||
}
|
||||
@@ -1180,13 +1188,15 @@ class CodePathState {
|
||||
makeLooped(
|
||||
this,
|
||||
context.endOfUpdateSegments,
|
||||
context.testSegments);
|
||||
context.testSegments
|
||||
);
|
||||
}
|
||||
} else if (context.testSegments) {
|
||||
finalizeTestSegmentsOfFor(
|
||||
context,
|
||||
choiceContext,
|
||||
forkContext.head);
|
||||
forkContext.head
|
||||
);
|
||||
} else {
|
||||
context.endOfInitSegments = forkContext.head;
|
||||
}
|
||||
|
||||
@@ -51,7 +51,8 @@ class CodePath {
|
||||
Object.defineProperty(
|
||||
this,
|
||||
"internal",
|
||||
{ value: new CodePathState(new IdGenerator(`${id}_`), onLooped) });
|
||||
{ value: new CodePathState(new IdGenerator(`${id}_`), onLooped) }
|
||||
);
|
||||
|
||||
// Adds this into `childCodePaths` of `upper`.
|
||||
if (upper) {
|
||||
@@ -205,7 +206,7 @@ class CodePath {
|
||||
|
||||
// Call the callback when the first time.
|
||||
if (!skippedSegment) {
|
||||
callback.call(this, segment, controller); // eslint-disable-line callback-return
|
||||
callback.call(this, segment, controller);
|
||||
if (segment === lastSegment) {
|
||||
controller.skip();
|
||||
}
|
||||
|
||||
@@ -254,7 +254,8 @@ class ForkContext {
|
||||
return new ForkContext(
|
||||
parentContext.idGenerator,
|
||||
parentContext,
|
||||
(forkLeavingPath ? 2 : 1) * parentContext.count);
|
||||
(forkLeavingPath ? 2 : 1) * parentContext.count
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -24,7 +24,7 @@ const debug = require("debug")("eslint:config");
|
||||
// Constants
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const PERSONAL_CONFIG_DIR = os.homedir() || null;
|
||||
const PERSONAL_CONFIG_DIR = os.homedir();
|
||||
const SUBCONFIG_SEP = ":";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -148,15 +148,13 @@ class Config {
|
||||
getPersonalConfig() {
|
||||
if (typeof this.personalConfig === "undefined") {
|
||||
let config;
|
||||
const filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR);
|
||||
|
||||
if (PERSONAL_CONFIG_DIR) {
|
||||
const filename = ConfigFile.getFilenameForDirectory(PERSONAL_CONFIG_DIR);
|
||||
|
||||
if (filename) {
|
||||
debug("Using personal config");
|
||||
config = ConfigFile.load(filename, this);
|
||||
}
|
||||
if (filename) {
|
||||
debug("Using personal config");
|
||||
config = ConfigFile.load(filename, this);
|
||||
}
|
||||
|
||||
this.personalConfig = config || null;
|
||||
}
|
||||
|
||||
@@ -351,10 +349,8 @@ class Config {
|
||||
config = ConfigOps.merge(config, { parser: this.parser });
|
||||
}
|
||||
|
||||
// Step 4: Apply environments to the config if present
|
||||
if (config.env) {
|
||||
config = ConfigOps.applyEnvironments(config, this.linterContext.environments);
|
||||
}
|
||||
// Step 4: Apply environments to the config
|
||||
config = ConfigOps.applyEnvironments(config, this.linterContext.environments);
|
||||
|
||||
this.configCache.setMergedConfig(vector, config);
|
||||
|
||||
|
||||
@@ -269,10 +269,8 @@ class Registry {
|
||||
* @returns {Registry} New registry with errorCount populated
|
||||
*/
|
||||
lintSourceCode(sourceCodes, config, cb) {
|
||||
let ruleSetIdx,
|
||||
lintedRegistry;
|
||||
let lintedRegistry = new Registry();
|
||||
|
||||
lintedRegistry = new Registry();
|
||||
lintedRegistry.rules = Object.assign({}, this.rules);
|
||||
|
||||
const ruleSets = lintedRegistry.buildRuleSets();
|
||||
@@ -287,7 +285,7 @@ class Registry {
|
||||
filenames.forEach(filename => {
|
||||
debug(`Linting file: ${filename}`);
|
||||
|
||||
ruleSetIdx = 0;
|
||||
let ruleSetIdx = 0;
|
||||
|
||||
ruleSets.forEach(ruleSet => {
|
||||
const lintConfig = Object.assign({}, config, { rules: ruleSet });
|
||||
|
||||
@@ -3,8 +3,6 @@
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
|
||||
/* eslint no-use-before-define: 0 */
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -418,6 +416,8 @@ function applyExtends(config, configContext, filePath, relativeTo) {
|
||||
);
|
||||
}
|
||||
debug(`Loading ${parentPath}`);
|
||||
|
||||
// eslint-disable-next-line no-use-before-define
|
||||
return ConfigOps.merge(load(parentPath, configContext, relativeTo), previousValue);
|
||||
} catch (e) {
|
||||
|
||||
@@ -502,8 +502,8 @@ function resolve(filePath, relativeTo) {
|
||||
|
||||
if (filePath.startsWith("plugin:")) {
|
||||
const configFullName = filePath;
|
||||
const pluginName = filePath.substr(7, filePath.lastIndexOf("/") - 7);
|
||||
const configName = filePath.substr(filePath.lastIndexOf("/") + 1, filePath.length - filePath.lastIndexOf("/") - 1);
|
||||
const pluginName = filePath.slice(7, filePath.lastIndexOf("/"));
|
||||
const configName = filePath.slice(filePath.lastIndexOf("/") + 1);
|
||||
|
||||
normalizedPackageName = normalizePackageName(pluginName, "eslint-plugin");
|
||||
debug(`Attempting to resolve ${normalizedPackageName}`);
|
||||
@@ -546,7 +546,7 @@ function loadFromDisk(resolvedPath, configContext) {
|
||||
}
|
||||
|
||||
// validate the configuration before continuing
|
||||
validator.validate(config, resolvedPath, configContext.linterContext.rules, configContext.linterContext.environments);
|
||||
validator.validate(config, resolvedPath.configFullName, configContext.linterContext.rules, configContext.linterContext.environments);
|
||||
|
||||
/*
|
||||
* If an `extends` property is defined, it represents a configuration file to use as
|
||||
|
||||
@@ -65,6 +65,7 @@ function writeFile(config, format) {
|
||||
* @param {string} moduleName The module name to get.
|
||||
* @returns {Object} The peer dependencies of the given module.
|
||||
* This object is the object of `peerDependencies` field of `package.json`.
|
||||
* Returns null if npm was not found.
|
||||
*/
|
||||
function getPeerDependencies(moduleName) {
|
||||
let result = getPeerDependencies.cache.get(moduleName);
|
||||
@@ -356,7 +357,8 @@ function hasESLintVersionConflict(answers) {
|
||||
// Get the required range of ESLint version.
|
||||
const configName = getStyleGuideName(answers);
|
||||
const moduleName = `eslint-config-${configName}@latest`;
|
||||
const requiredESLintVersionRange = getPeerDependencies(moduleName).eslint;
|
||||
const peerDependencies = getPeerDependencies(moduleName) || {};
|
||||
const requiredESLintVersionRange = peerDependencies.eslint;
|
||||
|
||||
if (!requiredESLintVersionRange) {
|
||||
return false;
|
||||
@@ -380,7 +382,6 @@ function hasESLintVersionConflict(answers) {
|
||||
* @returns {Promise} The promise with the result of the prompt
|
||||
*/
|
||||
function promptUser() {
|
||||
let config;
|
||||
|
||||
return inquirer.prompt([
|
||||
{
|
||||
@@ -467,7 +468,8 @@ function promptUser() {
|
||||
earlyAnswers.styleguide = "airbnb-base";
|
||||
}
|
||||
|
||||
config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint);
|
||||
const config = getConfigForStyleGuide(earlyAnswers.styleguide, earlyAnswers.installESLint);
|
||||
|
||||
writeFile(config, earlyAnswers.format);
|
||||
|
||||
return void 0;
|
||||
@@ -527,7 +529,8 @@ function promptUser() {
|
||||
if (earlyAnswers.source === "auto") {
|
||||
const combinedAnswers = Object.assign({}, earlyAnswers, secondAnswers);
|
||||
|
||||
config = processAnswers(combinedAnswers);
|
||||
const config = processAnswers(combinedAnswers);
|
||||
|
||||
installModules(config);
|
||||
writeFile(config, earlyAnswers.format);
|
||||
|
||||
@@ -573,7 +576,8 @@ function promptUser() {
|
||||
]).then(answers => {
|
||||
const totalAnswers = Object.assign({}, earlyAnswers, secondAnswers, answers);
|
||||
|
||||
config = processAnswers(totalAnswers);
|
||||
const config = processAnswers(totalAnswers);
|
||||
|
||||
installModules(config);
|
||||
writeFile(config, answers.format);
|
||||
});
|
||||
|
||||
@@ -193,25 +193,25 @@ module.exports = {
|
||||
},
|
||||
|
||||
/**
|
||||
* Converts new-style severity settings (off, warn, error) into old-style
|
||||
* severity settings (0, 1, 2) for all rules. Assumption is that severity
|
||||
* values have already been validated as correct.
|
||||
* @param {Object} config The config object to normalize.
|
||||
* @returns {void}
|
||||
* Normalizes the severity value of a rule's configuration to a number
|
||||
* @param {(number|string|[number, ...*]|[string, ...*])} ruleConfig A rule's configuration value, generally
|
||||
* received from the user. A valid config value is either 0, 1, 2, the string "off" (treated the same as 0),
|
||||
* the string "warn" (treated the same as 1), the string "error" (treated the same as 2), or an array
|
||||
* whose first element is one of the above values. Strings are matched case-insensitively.
|
||||
* @returns {(0|1|2)} The numeric severity value if the config value was valid, otherwise 0.
|
||||
*/
|
||||
normalize(config) {
|
||||
getRuleSeverity(ruleConfig) {
|
||||
const severityValue = Array.isArray(ruleConfig) ? ruleConfig[0] : ruleConfig;
|
||||
|
||||
if (config.rules) {
|
||||
Object.keys(config.rules).forEach(ruleId => {
|
||||
const ruleConfig = config.rules[ruleId];
|
||||
|
||||
if (typeof ruleConfig === "string") {
|
||||
config.rules[ruleId] = RULE_SEVERITY[ruleConfig.toLowerCase()] || 0;
|
||||
} else if (Array.isArray(ruleConfig) && typeof ruleConfig[0] === "string") {
|
||||
ruleConfig[0] = RULE_SEVERITY[ruleConfig[0].toLowerCase()] || 0;
|
||||
}
|
||||
});
|
||||
if (severityValue === 0 || severityValue === 1 || severityValue === 2) {
|
||||
return severityValue;
|
||||
}
|
||||
|
||||
if (typeof severityValue === "string") {
|
||||
return RULE_SEVERITY[severityValue.toLowerCase()] || 0;
|
||||
}
|
||||
|
||||
return 0;
|
||||
},
|
||||
|
||||
/**
|
||||
|
||||
@@ -10,6 +10,7 @@
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const ajv = require("../util/ajv"),
|
||||
lodash = require("lodash"),
|
||||
configSchema = require("../../conf/config-schema.js"),
|
||||
util = require("util");
|
||||
|
||||
@@ -179,6 +180,25 @@ function formatErrors(errors) {
|
||||
}).map(message => `\t- ${message}.\n`).join("");
|
||||
}
|
||||
|
||||
/**
|
||||
* Emits a deprecation warning containing a given filepath. A new deprecation warning is emitted
|
||||
* for each unique file path, but repeated invocations with the same file path have no effect.
|
||||
* No warnings are emitted if the `--no-deprecation` or `--no-warnings` Node runtime flags are active.
|
||||
* @param {string} source The name of the configuration source to report the warning for.
|
||||
* @returns {void}
|
||||
*/
|
||||
const emitEcmaFeaturesWarning = lodash.memoize(source => {
|
||||
|
||||
/*
|
||||
* util.deprecate seems to be the only way to emit a warning in Node 4.x while respecting the --no-warnings flag.
|
||||
* (In Node 6+, process.emitWarning could be used instead.)
|
||||
*/
|
||||
util.deprecate(
|
||||
() => {},
|
||||
`[eslint] The 'ecmaFeatures' config file property is deprecated, and has no effect. (found in ${source})`
|
||||
)();
|
||||
});
|
||||
|
||||
/**
|
||||
* Validates the top level properties of the config object.
|
||||
* @param {Object} config The config object to validate.
|
||||
@@ -189,7 +209,11 @@ function validateConfigSchema(config, source) {
|
||||
validateSchema = validateSchema || ajv.compile(configSchema);
|
||||
|
||||
if (!validateSchema(config)) {
|
||||
throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validateSchema.errors)}`);
|
||||
throw new Error(`ESLint configuration in ${source} is invalid:\n${formatErrors(validateSchema.errors)}`);
|
||||
}
|
||||
|
||||
if (Object.prototype.hasOwnProperty.call(config, "ecmaFeatures")) {
|
||||
emitEcmaFeaturesWarning(source);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -43,7 +43,7 @@ class Plugins {
|
||||
* @returns {string} The name of the plugin without prefix.
|
||||
*/
|
||||
static removePrefix(pluginName) {
|
||||
return pluginName.startsWith(PLUGIN_NAME_PREFIX) ? pluginName.substring(PLUGIN_NAME_PREFIX.length) : pluginName;
|
||||
return pluginName.startsWith(PLUGIN_NAME_PREFIX) ? pluginName.slice(PLUGIN_NAME_PREFIX.length) : pluginName;
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -47,7 +47,7 @@ function formatFilePath(filePath, line, column) {
|
||||
*/
|
||||
function formatMessage(message, parentResult) {
|
||||
const type = (message.fatal || message.severity === 2) ? chalk.red("error") : chalk.yellow("warning");
|
||||
const msg = `${chalk.bold(message.message.replace(/\.$/, ""))}`;
|
||||
const msg = `${chalk.bold(message.message.replace(/([^ ])\.$/, "$1"))}`;
|
||||
const ruleId = message.fatal ? "" : chalk.dim(`(${message.ruleId})`);
|
||||
const filePath = formatFilePath(parentResult.filePath, message.line, message.column);
|
||||
const sourceCode = parentResult.output ? parentResult.output : parentResult.source;
|
||||
|
||||
@@ -3,6 +3,6 @@
|
||||
<td class="clr-<%= severityNumber %>"><%= severityName %></td>
|
||||
<td><%- message %></td>
|
||||
<td>
|
||||
<a href="http://eslint.org/docs/rules/<%= ruleId %>" target="_blank"><%= ruleId %></a>
|
||||
<a href="https://eslint.org/docs/rules/<%= ruleId %>" target="_blank"><%= ruleId %></a>
|
||||
</td>
|
||||
</tr>
|
||||
@@ -1,5 +1,7 @@
|
||||
<!DOCTYPE html>
|
||||
<html>
|
||||
<head>
|
||||
<meta charset="UTF-8">
|
||||
<title>ESLint Report</title>
|
||||
<style>
|
||||
body {
|
||||
@@ -29,7 +31,7 @@
|
||||
}
|
||||
th {
|
||||
font-weight:400;
|
||||
font-size:normal;
|
||||
font-size:medium;
|
||||
text-align:left;
|
||||
cursor:pointer
|
||||
}
|
||||
|
||||
@@ -39,10 +39,7 @@ module.exports = function(results) {
|
||||
|
||||
const messages = result.messages;
|
||||
|
||||
if (messages.length) {
|
||||
output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`;
|
||||
}
|
||||
|
||||
output += `<testsuite package="org.eslint" time="0" tests="${messages.length}" errors="${messages.length}" name="${result.filePath}">\n`;
|
||||
messages.forEach(message => {
|
||||
const type = message.fatal ? "error" : "failure";
|
||||
|
||||
@@ -57,10 +54,7 @@ module.exports = function(results) {
|
||||
output += `</${type}>`;
|
||||
output += "</testcase>\n";
|
||||
});
|
||||
|
||||
if (messages.length) {
|
||||
output += "</testsuite>\n";
|
||||
}
|
||||
output += "</testsuite>\n";
|
||||
|
||||
});
|
||||
|
||||
|
||||
@@ -5,6 +5,7 @@
|
||||
"use strict";
|
||||
|
||||
const chalk = require("chalk"),
|
||||
stripAnsi = require("strip-ansi"),
|
||||
table = require("text-table");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
@@ -64,14 +65,14 @@ module.exports = function(results) {
|
||||
message.line || 0,
|
||||
message.column || 0,
|
||||
messageType,
|
||||
message.message.replace(/\.$/, ""),
|
||||
message.message.replace(/([^ ])\.$/, "$1"),
|
||||
chalk.dim(message.ruleId || "")
|
||||
];
|
||||
}),
|
||||
{
|
||||
align: ["", "r", "l"],
|
||||
stringLength(str) {
|
||||
return chalk.stripColor(str).length;
|
||||
return stripAnsi(str).length;
|
||||
}
|
||||
}
|
||||
).split("\n").map(el => el.replace(/(\d+)\s+(\d+)/, (m, p1, p2) => chalk.dim(`${p1}:${p2}`))).join("\n")}\n\n`;
|
||||
|
||||
@@ -47,8 +47,6 @@ const DEFAULT_OPTIONS = {
|
||||
* @returns {string} Path of ignore file or an empty string.
|
||||
*/
|
||||
function findFile(cwd, name) {
|
||||
cwd = cwd || DEFAULT_OPTIONS.cwd;
|
||||
|
||||
const ignoreFilePath = path.resolve(cwd, name);
|
||||
|
||||
return fs.existsSync(ignoreFilePath) && fs.statSync(ignoreFilePath).isFile() ? ignoreFilePath : "";
|
||||
|
||||
+478
-656
File diff suppressed because it is too large.
Load diff
@@ -15,7 +15,7 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
info() {
|
||||
console.log.apply(console, Array.prototype.slice.call(arguments));
|
||||
console.log.apply(console, arguments);
|
||||
},
|
||||
|
||||
/**
|
||||
@@ -23,6 +23,6 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
error() {
|
||||
console.error.apply(console, Array.prototype.slice.call(arguments));
|
||||
console.error.apply(console, arguments);
|
||||
}
|
||||
};
|
||||
@@ -214,6 +214,12 @@ module.exports = optionator({
|
||||
default: "true",
|
||||
description: "Prevent comments from changing config or rules"
|
||||
},
|
||||
{
|
||||
option: "report-unused-disable-directives",
|
||||
type: "Boolean",
|
||||
default: false,
|
||||
description: "Adds reported errors for unused eslint-disable directives"
|
||||
},
|
||||
{
|
||||
option: "print-config",
|
||||
type: "path::String",
|
||||
|
||||
@@ -0,0 +1,274 @@
|
||||
/**
|
||||
* @fileoverview A helper that translates context.report() calls from the rule API into generic problem objects
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const assert = require("assert");
|
||||
const ruleFixer = require("./util/rule-fixer");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* An error message description
|
||||
* @typedef {Object} MessageDescriptor
|
||||
* @property {ASTNode} [node] The reported node
|
||||
* @property {Location} loc The location of the problem.
|
||||
* @property {string} message The problem message.
|
||||
* @property {Object} [data] Optional data to use to fill in placeholders in the
|
||||
* message.
|
||||
* @property {Function} [fix] The function to call that creates a fix command.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Module Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
|
||||
/**
|
||||
* Translates a multi-argument context.report() call into a single object argument call
|
||||
* @param {...*} arguments A list of arguments passed to `context.report`
|
||||
* @returns {MessageDescriptor} A normalized object containing report information
|
||||
*/
|
||||
function normalizeMultiArgReportCall() {
|
||||
|
||||
// If there is one argument, it is considered to be a new-style call already.
|
||||
if (arguments.length === 1) {
|
||||
return arguments[0];
|
||||
}
|
||||
|
||||
// If the second argument is a string, the arguments are interpreted as [node, message, data, fix].
|
||||
if (typeof arguments[1] === "string") {
|
||||
return {
|
||||
node: arguments[0],
|
||||
message: arguments[1],
|
||||
data: arguments[2],
|
||||
fix: arguments[3]
|
||||
};
|
||||
}
|
||||
|
||||
// Otherwise, the arguments are interpreted as [node, loc, message, data, fix].
|
||||
return {
|
||||
node: arguments[0],
|
||||
loc: arguments[1],
|
||||
message: arguments[2],
|
||||
data: arguments[3],
|
||||
fix: arguments[4]
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
* Asserts that either a loc or a node was provided, and the node is valid if it was provided.
|
||||
* @param {MessageDescriptor} descriptor A descriptor to validate
|
||||
* @returns {void}
|
||||
* @throws AssertionError if neither a node nor a loc was provided, or if the node is not an object
|
||||
*/
|
||||
function assertValidNodeInfo(descriptor) {
|
||||
if (descriptor.node) {
|
||||
assert(typeof descriptor.node === "object", "Node must be an object");
|
||||
} else {
|
||||
assert(descriptor.loc, "Node must be provided when reporting error if location is not provided");
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Normalizes a MessageDescriptor to always have a `loc` with `start` and `end` properties
|
||||
* @param {MessageDescriptor} descriptor A descriptor for the report from a rule.
|
||||
* @returns {{start: Location, end: (Location|null)}} An updated location that infers the `start` and `end` properties
|
||||
* from the `node` of the original descriptor, or infers the `start` from the `loc` of the original descriptor.
|
||||
*/
|
||||
function normalizeReportLoc(descriptor) {
|
||||
if (descriptor.loc) {
|
||||
if (descriptor.loc.start) {
|
||||
return descriptor.loc;
|
||||
}
|
||||
return { start: descriptor.loc, end: null };
|
||||
}
|
||||
return descriptor.node.loc;
|
||||
}
|
||||
|
||||
/**
|
||||
* Interpolates data placeholders in report messages
|
||||
* @param {MessageDescriptor} descriptor The report message descriptor.
|
||||
* @returns {string} The interpolated message for the descriptor
|
||||
*/
|
||||
function normalizeMessagePlaceholders(descriptor) {
|
||||
if (!descriptor.data) {
|
||||
return descriptor.message;
|
||||
}
|
||||
return descriptor.message.replace(/\{\{\s*([^{}]+?)\s*\}\}/g, (fullMatch, term) => {
|
||||
if (term in descriptor.data) {
|
||||
return descriptor.data[term];
|
||||
}
|
||||
|
||||
return fullMatch;
|
||||
});
|
||||
}
|
||||
|
||||
/**
|
||||
* Compares items in a fixes array by range.
|
||||
* @param {Fix} a The first message.
|
||||
* @param {Fix} b The second message.
|
||||
* @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
|
||||
* @private
|
||||
*/
|
||||
function compareFixesByRange(a, b) {
|
||||
return a.range[0] - b.range[0] || a.range[1] - b.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given fixes array into one.
|
||||
* @param {Fix[]} fixes The fixes to merge.
|
||||
* @param {SourceCode} sourceCode The source code object to get the text between fixes.
|
||||
* @returns {{text: string, range: [number, number]}} The merged fixes
|
||||
*/
|
||||
function mergeFixes(fixes, sourceCode) {
|
||||
if (fixes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (fixes.length === 1) {
|
||||
return fixes[0];
|
||||
}
|
||||
|
||||
fixes.sort(compareFixesByRange);
|
||||
|
||||
const originalText = sourceCode.text;
|
||||
const start = fixes[0].range[0];
|
||||
const end = fixes[fixes.length - 1].range[1];
|
||||
let text = "";
|
||||
let lastPos = Number.MIN_SAFE_INTEGER;
|
||||
|
||||
for (const fix of fixes) {
|
||||
assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report.");
|
||||
|
||||
if (fix.range[0] >= 0) {
|
||||
text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]);
|
||||
}
|
||||
text += fix.text;
|
||||
lastPos = fix.range[1];
|
||||
}
|
||||
text += originalText.slice(Math.max(0, start, lastPos), end);
|
||||
|
||||
return { range: [start, end], text };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one fix object from the given descriptor.
|
||||
* If the descriptor retrieves multiple fixes, this merges those to one.
|
||||
* @param {MessageDescriptor} descriptor The report descriptor.
|
||||
* @param {SourceCode} sourceCode The source code object to get text between fixes.
|
||||
* @returns {({text: string, range: [number, number]}|null)} The fix for the descriptor
|
||||
*/
|
||||
function normalizeFixes(descriptor, sourceCode) {
|
||||
if (typeof descriptor.fix !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// @type {null | Fix | Fix[] | IterableIterator<Fix>}
|
||||
const fix = descriptor.fix(ruleFixer);
|
||||
|
||||
// Merge to one.
|
||||
if (fix && Symbol.iterator in fix) {
|
||||
return mergeFixes(Array.from(fix), sourceCode);
|
||||
}
|
||||
return fix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates information about the report from a descriptor
|
||||
* @param {{
|
||||
* ruleId: string,
|
||||
* severity: (0|1|2),
|
||||
* node: (ASTNode|null),
|
||||
* message: string,
|
||||
* loc: {start: SourceLocation, end: (SourceLocation|null)},
|
||||
* fix: ({text: string, range: [number, number]}|null),
|
||||
* sourceLines: string[]
|
||||
* }} options Information about the problem
|
||||
* @returns {function(...args): {
|
||||
* ruleId: string,
|
||||
* severity: (0|1|2),
|
||||
* message: string,
|
||||
* line: number,
|
||||
* column: number,
|
||||
* endLine: (number|undefined),
|
||||
* endColumn: (number|undefined),
|
||||
* nodeType: (string|null),
|
||||
* source: string,
|
||||
* fix: ({text: string, range: [number, number]}|null)
|
||||
* }} Information about the report
|
||||
*/
|
||||
function createProblem(options) {
|
||||
const problem = {
|
||||
ruleId: options.ruleId,
|
||||
severity: options.severity,
|
||||
message: options.message,
|
||||
line: options.loc.start.line,
|
||||
column: options.loc.start.column + 1,
|
||||
nodeType: options.node && options.node.type || null,
|
||||
source: options.sourceLines[options.loc.start.line - 1] || ""
|
||||
};
|
||||
|
||||
if (options.loc.end) {
|
||||
problem.endLine = options.loc.end.line;
|
||||
problem.endColumn = options.loc.end.column + 1;
|
||||
}
|
||||
|
||||
if (options.fix) {
|
||||
problem.fix = options.fix;
|
||||
}
|
||||
|
||||
return problem;
|
||||
}
|
||||
|
||||
/**
|
||||
* Returns a function that converts the arguments of a `context.report` call from a rule into a reported
|
||||
* problem for the Node.js API.
|
||||
* @param {{ruleId: string, severity: number, sourceCode: SourceCode}} metadata Metadata for the reported problem
|
||||
* @param {SourceCode} sourceCode The `SourceCode` instance for the text being linted
|
||||
* @returns {function(...args): {
|
||||
* ruleId: string,
|
||||
* severity: (0|1|2),
|
||||
* message: string,
|
||||
* line: number,
|
||||
* column: number,
|
||||
* endLine: (number|undefined),
|
||||
* endColumn: (number|undefined),
|
||||
* nodeType: (string|null),
|
||||
* source: string,
|
||||
* fix: ({text: string, range: [number, number]}|null)
|
||||
* }}
|
||||
* Information about the report
|
||||
*/
|
||||
|
||||
module.exports = function createReportTranslator(metadata) {
|
||||
|
||||
/*
|
||||
* `createReportTranslator` gets called once per enabled rule per file. It needs to be very performant.
|
||||
* The report translator itself (i.e. the function that `createReportTranslator` returns) gets
|
||||
* called every time a rule reports a problem, which happens much less frequently (usually, the vast
|
||||
* majority of rules don't report any problems for a given file).
|
||||
*/
|
||||
return function() {
|
||||
const descriptor = normalizeMultiArgReportCall.apply(null, arguments);
|
||||
|
||||
assertValidNodeInfo(descriptor);
|
||||
|
||||
return createProblem({
|
||||
ruleId: metadata.ruleId,
|
||||
severity: metadata.severity,
|
||||
node: descriptor.node,
|
||||
message: normalizeMessagePlaceholders(descriptor),
|
||||
loc: normalizeReportLoc(descriptor),
|
||||
fix: normalizeFixes(descriptor, metadata.sourceCode),
|
||||
sourceLines: metadata.sourceCode.lines
|
||||
});
|
||||
};
|
||||
};
|
||||
@@ -1,229 +0,0 @@
|
||||
/**
|
||||
* @fileoverview RuleContext utility for rules
|
||||
* @author Nicholas C. Zakas
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const assert = require("assert");
|
||||
const ruleFixer = require("./util/rule-fixer");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Constants
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const PASSTHROUGHS = [
|
||||
"getAncestors",
|
||||
"getDeclaredVariables",
|
||||
"getFilename",
|
||||
"getScope",
|
||||
"markVariableAsUsed",
|
||||
|
||||
// DEPRECATED
|
||||
"getAllComments",
|
||||
"getComments",
|
||||
"getFirstToken",
|
||||
"getFirstTokens",
|
||||
"getJSDocComment",
|
||||
"getLastToken",
|
||||
"getLastTokens",
|
||||
"getNodeByRangeIndex",
|
||||
"getSource",
|
||||
"getSourceLines",
|
||||
"getTokenAfter",
|
||||
"getTokenBefore",
|
||||
"getTokenByRangeStart",
|
||||
"getTokens",
|
||||
"getTokensAfter",
|
||||
"getTokensBefore",
|
||||
"getTokensBetween"
|
||||
];
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* An error message description
|
||||
* @typedef {Object} MessageDescriptor
|
||||
* @property {string} nodeType The type of node.
|
||||
* @property {Location} loc The location of the problem.
|
||||
* @property {string} message The problem message.
|
||||
* @property {Object} [data] Optional data to use to fill in placeholders in the
|
||||
* message.
|
||||
* @property {Function} fix The function to call that creates a fix command.
|
||||
*/
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Compares items in a fixes array by range.
|
||||
* @param {Fix} a The first message.
|
||||
* @param {Fix} b The second message.
|
||||
* @returns {int} -1 if a comes before b, 1 if a comes after b, 0 if equal.
|
||||
* @private
|
||||
*/
|
||||
function compareFixesByRange(a, b) {
|
||||
return a.range[0] - b.range[0] || a.range[1] - b.range[1];
|
||||
}
|
||||
|
||||
/**
|
||||
* Merges the given fixes array into one.
|
||||
* @param {Fix[]} fixes The fixes to merge.
|
||||
* @param {SourceCode} sourceCode The source code object to get the text between fixes.
|
||||
* @returns {void}
|
||||
*/
|
||||
function mergeFixes(fixes, sourceCode) {
|
||||
if (fixes.length === 0) {
|
||||
return null;
|
||||
}
|
||||
if (fixes.length === 1) {
|
||||
return fixes[0];
|
||||
}
|
||||
|
||||
fixes.sort(compareFixesByRange);
|
||||
|
||||
const originalText = sourceCode.text;
|
||||
const start = fixes[0].range[0];
|
||||
const end = fixes[fixes.length - 1].range[1];
|
||||
let text = "";
|
||||
let lastPos = Number.MIN_SAFE_INTEGER;
|
||||
|
||||
for (const fix of fixes) {
|
||||
assert(fix.range[0] >= lastPos, "Fix objects must not be overlapped in a report.");
|
||||
|
||||
if (fix.range[0] >= 0) {
|
||||
text += originalText.slice(Math.max(0, start, lastPos), fix.range[0]);
|
||||
}
|
||||
text += fix.text;
|
||||
lastPos = fix.range[1];
|
||||
}
|
||||
text += originalText.slice(Math.max(0, start, lastPos), end);
|
||||
|
||||
return { range: [start, end], text };
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets one fix object from the given descriptor.
|
||||
* If the descriptor retrieves multiple fixes, this merges those to one.
|
||||
* @param {Object} descriptor The report descriptor.
|
||||
* @param {SourceCode} sourceCode The source code object to get text between fixes.
|
||||
* @returns {Fix} The got fix object.
|
||||
*/
|
||||
function getFix(descriptor, sourceCode) {
|
||||
if (typeof descriptor.fix !== "function") {
|
||||
return null;
|
||||
}
|
||||
|
||||
// @type {null | Fix | Fix[] | IterableIterator<Fix>}
|
||||
const fix = descriptor.fix(ruleFixer);
|
||||
|
||||
// Merge to one.
|
||||
if (fix && Symbol.iterator in fix) {
|
||||
return mergeFixes(Array.from(fix), sourceCode);
|
||||
}
|
||||
return fix;
|
||||
}
|
||||
|
||||
/**
|
||||
* Rule context class
|
||||
* Acts as an abstraction layer between rules and the main eslint object.
|
||||
*/
|
||||
class RuleContext {
|
||||
|
||||
/**
|
||||
* @param {string} ruleId The ID of the rule using this object.
|
||||
* @param {eslint} eslint The eslint object.
|
||||
* @param {number} severity The configured severity level of the rule.
|
||||
* @param {Array} options The configuration information to be added to the rule.
|
||||
* @param {Object} settings The configuration settings passed from the config file.
|
||||
* @param {Object} parserOptions The parserOptions settings passed from the config file.
|
||||
* @param {Object} parserPath The parser setting passed from the config file.
|
||||
* @param {Object} meta The metadata of the rule
|
||||
* @param {Object} parserServices The parser services for the rule.
|
||||
*/
|
||||
constructor(ruleId, eslint, severity, options, settings, parserOptions, parserPath, meta, parserServices) {
|
||||
|
||||
// public.
|
||||
this.id = ruleId;
|
||||
this.options = options;
|
||||
this.settings = settings;
|
||||
this.parserOptions = parserOptions;
|
||||
this.parserPath = parserPath;
|
||||
this.meta = meta;
|
||||
|
||||
// create a separate copy and freeze it (it's not nice to freeze other people's objects)
|
||||
this.parserServices = Object.freeze(Object.assign({}, parserServices));
|
||||
|
||||
// private.
|
||||
this.eslint = eslint;
|
||||
this.severity = severity;
|
||||
|
||||
Object.freeze(this);
|
||||
}
|
||||
|
||||
/**
|
||||
* Passthrough to eslint.getSourceCode().
|
||||
* @returns {SourceCode} The SourceCode object for the code.
|
||||
*/
|
||||
getSourceCode() {
|
||||
return this.eslint.getSourceCode();
|
||||
}
|
||||
|
||||
/**
|
||||
* Passthrough to eslint.report() that automatically assigns the rule ID and severity.
|
||||
* @param {ASTNode|MessageDescriptor} nodeOrDescriptor The AST node related to the message or a message
|
||||
* descriptor.
|
||||
* @param {Object=} location The location of the error.
|
||||
* @param {string} message The message to display to the user.
|
||||
* @param {Object} opts Optional template data which produces a formatted message
|
||||
* with symbols being replaced by this object's values.
|
||||
* @returns {void}
|
||||
*/
|
||||
report(nodeOrDescriptor, location, message, opts) {
|
||||
|
||||
// check to see if it's a new style call
|
||||
if (arguments.length === 1) {
|
||||
const descriptor = nodeOrDescriptor;
|
||||
const fix = getFix(descriptor, this.getSourceCode());
|
||||
|
||||
this.eslint.report(
|
||||
this.id,
|
||||
this.severity,
|
||||
descriptor.node,
|
||||
descriptor.loc || descriptor.node.loc.start,
|
||||
descriptor.message,
|
||||
descriptor.data,
|
||||
fix,
|
||||
this.meta
|
||||
);
|
||||
|
||||
return;
|
||||
}
|
||||
|
||||
// old style call
|
||||
this.eslint.report(
|
||||
this.id,
|
||||
this.severity,
|
||||
nodeOrDescriptor,
|
||||
location,
|
||||
message,
|
||||
opts,
|
||||
this.meta
|
||||
);
|
||||
}
|
||||
}
|
||||
|
||||
// Copy over passthrough methods. All functions will have 5 or fewer parameters.
|
||||
PASSTHROUGHS.forEach(function(name) {
|
||||
this[name] = function(a, b, c, d, e) {
|
||||
return this.eslint[name](a, b, c, d, e);
|
||||
};
|
||||
}, RuleContext.prototype);
|
||||
|
||||
module.exports = RuleContext;
|
||||
@@ -9,7 +9,48 @@
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const lodash = require("lodash");
|
||||
const loadRules = require("./load-rules");
|
||||
const ruleReplacements = require("../conf/replacements").rules;
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Creates a stub rule that gets used when a rule with a given ID is not found.
|
||||
* @param {string} ruleId The ID of the missing rule
|
||||
* @returns {{create: function(RuleContext): Object}} A rule that reports an error at the first location
|
||||
* in the program. The report has the message `Definition for rule '${ruleId}' was not found` if the rule is unknown,
|
||||
* or `Rule '${ruleId}' was removed and replaced by: ${replacements.join(", ")}` if the rule is known to have been
|
||||
* replaced.
|
||||
*/
|
||||
const createMissingRule = lodash.memoize(ruleId => {
|
||||
const message = Object.prototype.hasOwnProperty.call(ruleReplacements, ruleId)
|
||||
? `Rule '${ruleId}' was removed and replaced by: ${ruleReplacements[ruleId].join(", ")}`
|
||||
: `Definition for rule '${ruleId}' was not found`;
|
||||
|
||||
return {
|
||||
create: context => ({
|
||||
Program() {
|
||||
context.report({
|
||||
loc: { line: 1, column: 0 },
|
||||
message
|
||||
});
|
||||
}
|
||||
})
|
||||
};
|
||||
});
|
||||
|
||||
/**
|
||||
* Normalizes a rule module to the new-style API
|
||||
* @param {(Function|{create: Function})} rule A rule object, which can either be a function
|
||||
* ("old-style") or an object with a `create` method ("new-style")
|
||||
* @returns {{create: Function}} A new-style rule.
|
||||
*/
|
||||
function normalizeRule(rule) {
|
||||
return typeof rule === "function" ? Object.assign({ create: rule }, rule) : rule;
|
||||
}
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
@@ -29,7 +70,7 @@ class Rules {
|
||||
* @returns {void}
|
||||
*/
|
||||
define(ruleId, ruleModule) {
|
||||
this._rules[ruleId] = ruleModule;
|
||||
this._rules[ruleId] = normalizeRule(ruleModule);
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -66,11 +107,15 @@ class Rules {
|
||||
/**
|
||||
* Access rule handler by id (file name).
|
||||
* @param {string} ruleId Rule id (file name).
|
||||
* @returns {Function} Rule handler.
|
||||
* @returns {{create: Function, schema: JsonSchema[]}}
|
||||
* A rule. This is normalized to always have the new-style shape with a `create` method.
|
||||
*/
|
||||
get(ruleId) {
|
||||
if (!Object.prototype.hasOwnProperty.call(this._rules, ruleId)) {
|
||||
return createMissingRule(ruleId);
|
||||
}
|
||||
if (typeof this._rules[ruleId] === "string") {
|
||||
return require(this._rules[ruleId]);
|
||||
return normalizeRule(require(this._rules[ruleId]));
|
||||
}
|
||||
return this._rules[ruleId];
|
||||
|
||||
|
||||
@@ -71,7 +71,7 @@ module.exports = {
|
||||
// https://github.com/eslint/eslint/issues/8834
|
||||
const closingParenToken = sourceCode.getTokenAfter(paramToken, astUtils.isClosingParenToken);
|
||||
const asyncToken = isAsync ? sourceCode.getTokenBefore(firstTokenOfParam) : null;
|
||||
const shouldAddSpaceForAsync = asyncToken && (asyncToken.end === firstTokenOfParam.start);
|
||||
const shouldAddSpaceForAsync = asyncToken && (asyncToken.range[1] === firstTokenOfParam.range[0]);
|
||||
|
||||
return fixer.replaceTextRange([
|
||||
firstTokenOfParam.range[0],
|
||||
|
||||
@@ -238,7 +238,7 @@ module.exports = {
|
||||
// `do while` expressions sometimes need a space to be inserted after `do`.
|
||||
// e.g. `do{foo()} while (bar)` should be corrected to `do foo() while (bar)`
|
||||
const needsPrecedingSpace = node.type === "DoWhileStatement" &&
|
||||
sourceCode.getTokenBefore(bodyNode).end === bodyNode.start &&
|
||||
sourceCode.getTokenBefore(bodyNode).range[1] === bodyNode.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent("do", sourceCode.getFirstToken(bodyNode, { skip: 1 }));
|
||||
|
||||
const openingBracket = sourceCode.getFirstToken(bodyNode);
|
||||
|
||||
@@ -54,46 +54,67 @@ module.exports = {
|
||||
allowPattern = new RegExp(options.allowPattern);
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if the property is valid dot notation
|
||||
* @param {ASTNode} node The dot notation node
|
||||
* @param {string} value Value which is to be checked
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkComputedProperty(node, value) {
|
||||
if (
|
||||
validIdentifier.test(value) &&
|
||||
(allowKeywords || keywords.indexOf(String(value)) === -1) &&
|
||||
!(allowPattern && allowPattern.test(value))
|
||||
) {
|
||||
const formattedValue = node.property.type === "Literal" ? JSON.stringify(value) : `\`${value}\``;
|
||||
|
||||
context.report({
|
||||
node: node.property,
|
||||
message: "[{{propertyValue}}] is better written in dot notation.",
|
||||
data: {
|
||||
propertyValue: formattedValue
|
||||
},
|
||||
fix(fixer) {
|
||||
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
|
||||
const rightBracket = sourceCode.getLastToken(node);
|
||||
|
||||
if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
|
||||
|
||||
// Don't perform any fixes if there are comments inside the brackets.
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
|
||||
const needsSpaceAfterProperty = tokenAfterProperty &&
|
||||
rightBracket.range[1] === tokenAfterProperty.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(String(value), tokenAfterProperty);
|
||||
|
||||
const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
|
||||
const textAfterProperty = needsSpaceAfterProperty ? " " : "";
|
||||
|
||||
return fixer.replaceTextRange(
|
||||
[leftBracket.range[0], rightBracket.range[1]],
|
||||
`${textBeforeDot}.${value}${textAfterProperty}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
return {
|
||||
MemberExpression(node) {
|
||||
if (
|
||||
node.computed &&
|
||||
node.property.type === "Literal" &&
|
||||
validIdentifier.test(node.property.value) &&
|
||||
(allowKeywords || keywords.indexOf(String(node.property.value)) === -1)
|
||||
node.property.type === "Literal"
|
||||
) {
|
||||
if (!(allowPattern && allowPattern.test(node.property.value))) {
|
||||
context.report({
|
||||
node: node.property,
|
||||
message: "[{{propertyValue}}] is better written in dot notation.",
|
||||
data: {
|
||||
propertyValue: JSON.stringify(node.property.value)
|
||||
},
|
||||
fix(fixer) {
|
||||
const leftBracket = sourceCode.getTokenAfter(node.object, astUtils.isOpeningBracketToken);
|
||||
const rightBracket = sourceCode.getLastToken(node);
|
||||
|
||||
if (sourceCode.getFirstTokenBetween(leftBracket, rightBracket, { includeComments: true, filter: astUtils.isCommentToken })) {
|
||||
|
||||
// Don't perform any fixes if there are comments inside the brackets.
|
||||
return null;
|
||||
}
|
||||
|
||||
const tokenAfterProperty = sourceCode.getTokenAfter(rightBracket);
|
||||
const needsSpaceAfterProperty = tokenAfterProperty &&
|
||||
rightBracket.range[1] === tokenAfterProperty.range[0] &&
|
||||
!astUtils.canTokensBeAdjacent(String(node.property.value), tokenAfterProperty);
|
||||
|
||||
const textBeforeDot = astUtils.isDecimalInteger(node.object) ? " " : "";
|
||||
const textAfterProperty = needsSpaceAfterProperty ? " " : "";
|
||||
|
||||
return fixer.replaceTextRange(
|
||||
[leftBracket.range[0], rightBracket.range[1]],
|
||||
`${textBeforeDot}.${node.property.value}${textAfterProperty}`
|
||||
);
|
||||
}
|
||||
});
|
||||
}
|
||||
checkComputedProperty(node, node.property.value);
|
||||
}
|
||||
if (
|
||||
node.computed &&
|
||||
node.property.type === "TemplateLiteral" &&
|
||||
node.property.expressions.length === 0
|
||||
) {
|
||||
checkComputedProperty(node, node.property.quasis[0].value.cooked);
|
||||
}
|
||||
if (
|
||||
!allowKeywords &&
|
||||
|
||||
@@ -0,0 +1,221 @@
|
||||
/**
|
||||
* @fileoverview enforce consistent line breaks inside function parentheses
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const astUtils = require("../ast-utils");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "enforce consistent line breaks inside function parentheses",
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
},
|
||||
fixable: "whitespace",
|
||||
schema: [
|
||||
{
|
||||
oneOf: [
|
||||
{
|
||||
enum: ["always", "never", "consistent", "multiline"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
minItems: {
|
||||
type: "integer",
|
||||
minimum: 0
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
}
|
||||
]
|
||||
},
|
||||
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
const rawOption = context.options[0] || "multiline";
|
||||
const multilineOption = rawOption === "multiline";
|
||||
const consistentOption = rawOption === "consistent";
|
||||
let minItems;
|
||||
|
||||
if (typeof rawOption === "object") {
|
||||
minItems = rawOption.minItems;
|
||||
} else if (rawOption === "always") {
|
||||
minItems = 0;
|
||||
} else if (rawOption === "never") {
|
||||
minItems = Infinity;
|
||||
} else {
|
||||
minItems = null;
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Helpers
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* Determines whether there should be newlines inside function parens
|
||||
* @param {ASTNode[]} elements The arguments or parameters in the list
|
||||
* @param {boolean} hasLeftNewline `true` if the left paren has a newline in the current code.
|
||||
* @returns {boolean} `true` if there should be newlines inside the function parens
|
||||
*/
|
||||
function shouldHaveNewlines(elements, hasLeftNewline) {
|
||||
if (multilineOption) {
|
||||
return elements.some((element, index) => index !== elements.length - 1 && element.loc.end.line !== elements[index + 1].loc.start.line);
|
||||
}
|
||||
if (consistentOption) {
|
||||
return hasLeftNewline;
|
||||
}
|
||||
return elements.length >= minItems;
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates a list of arguments or parameters
|
||||
* @param {Object} parens An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token
|
||||
* @param {ASTNode[]} elements The arguments or parameters in the list
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateParens(parens, elements) {
|
||||
const leftParen = parens.leftParen;
|
||||
const rightParen = parens.rightParen;
|
||||
const tokenAfterLeftParen = sourceCode.getTokenAfter(leftParen);
|
||||
const tokenBeforeRightParen = sourceCode.getTokenBefore(rightParen);
|
||||
const hasLeftNewline = !astUtils.isTokenOnSameLine(leftParen, tokenAfterLeftParen);
|
||||
const hasRightNewline = !astUtils.isTokenOnSameLine(tokenBeforeRightParen, rightParen);
|
||||
const needsNewlines = shouldHaveNewlines(elements, hasLeftNewline);
|
||||
|
||||
if (hasLeftNewline && !needsNewlines) {
|
||||
context.report({
|
||||
node: leftParen,
|
||||
message: "Unexpected newline after '('.",
|
||||
fix(fixer) {
|
||||
return sourceCode.getText().slice(leftParen.range[1], tokenAfterLeftParen.range[0]).trim()
|
||||
|
||||
// If there is a comment between the ( and the first element, don't do a fix.
|
||||
? null
|
||||
: fixer.removeRange([leftParen.range[1], tokenAfterLeftParen.range[0]]);
|
||||
}
|
||||
});
|
||||
} else if (!hasLeftNewline && needsNewlines) {
|
||||
context.report({
|
||||
node: leftParen,
|
||||
message: "Expected a newline after '('.",
|
||||
fix: fixer => fixer.insertTextAfter(leftParen, "\n")
|
||||
});
|
||||
}
|
||||
|
||||
if (hasRightNewline && !needsNewlines) {
|
||||
context.report({
|
||||
node: rightParen,
|
||||
message: "Unexpected newline before ')'.",
|
||||
fix(fixer) {
|
||||
return sourceCode.getText().slice(tokenBeforeRightParen.range[1], rightParen.range[0]).trim()
|
||||
|
||||
// If there is a comment between the last element and the ), don't do a fix.
|
||||
? null
|
||||
: fixer.removeRange([tokenBeforeRightParen.range[1], rightParen.range[0]]);
|
||||
}
|
||||
});
|
||||
} else if (!hasRightNewline && needsNewlines) {
|
||||
context.report({
|
||||
node: rightParen,
|
||||
message: "Expected a newline before ')'.",
|
||||
fix: fixer => fixer.insertTextBefore(rightParen, "\n")
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the left paren and right paren tokens of a node.
|
||||
* @param {ASTNode} node The node with parens
|
||||
* @returns {Object} An object with keys `leftParen` for the left paren token, and `rightParen` for the right paren token.
|
||||
* Can also return `null` if an expression has no parens (e.g. a NewExpression with no arguments, or an ArrowFunctionExpression
|
||||
* with a single parameter)
|
||||
*/
|
||||
function getParenTokens(node) {
|
||||
switch (node.type) {
|
||||
case "NewExpression":
|
||||
if (!node.arguments.length && !(
|
||||
astUtils.isOpeningParenToken(sourceCode.getLastToken(node, { skip: 1 })) &&
|
||||
astUtils.isClosingParenToken(sourceCode.getLastToken(node))
|
||||
)) {
|
||||
|
||||
// If the NewExpression does not have parens (e.g. `new Foo`), return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
// falls through
|
||||
|
||||
case "CallExpression":
|
||||
return {
|
||||
leftParen: sourceCode.getTokenAfter(node.callee, astUtils.isOpeningParenToken),
|
||||
rightParen: sourceCode.getLastToken(node)
|
||||
};
|
||||
|
||||
case "FunctionDeclaration":
|
||||
case "FunctionExpression": {
|
||||
const leftParen = sourceCode.getFirstToken(node, astUtils.isOpeningParenToken);
|
||||
const rightParen = node.params.length
|
||||
? sourceCode.getTokenAfter(node.params[node.params.length - 1], astUtils.isClosingParenToken)
|
||||
: sourceCode.getTokenAfter(leftParen);
|
||||
|
||||
return { leftParen, rightParen };
|
||||
}
|
||||
|
||||
case "ArrowFunctionExpression": {
|
||||
const firstToken = sourceCode.getFirstToken(node);
|
||||
|
||||
if (!astUtils.isOpeningParenToken(firstToken)) {
|
||||
|
||||
// If the ArrowFunctionExpression has a single param without parens, return null.
|
||||
return null;
|
||||
}
|
||||
|
||||
return {
|
||||
leftParen: firstToken,
|
||||
rightParen: sourceCode.getTokenBefore(node.body, astUtils.isClosingParenToken)
|
||||
};
|
||||
}
|
||||
|
||||
default:
|
||||
throw new TypeError(`unexpected node with type ${node.type}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Validates the parentheses for a node
|
||||
* @param {ASTNode} node The node with parens
|
||||
* @returns {void}
|
||||
*/
|
||||
function validateNode(node) {
|
||||
const parens = getParenTokens(node);
|
||||
|
||||
if (parens) {
|
||||
validateParens(parens, astUtils.isFunction(node) ? node.params : node.arguments);
|
||||
}
|
||||
}
|
||||
|
||||
//----------------------------------------------------------------------
|
||||
// Public
|
||||
//----------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
ArrowFunctionExpression: validateNode,
|
||||
CallExpression: validateNode,
|
||||
FunctionDeclaration: validateNode,
|
||||
FunctionExpression: validateNode,
|
||||
NewExpression: validateNode
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -9,6 +9,22 @@
|
||||
// Rule Definition
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const OVERRIDE_SCHEMA = {
|
||||
oneOf: [
|
||||
{
|
||||
enum: ["before", "after", "both", "neither"]
|
||||
},
|
||||
{
|
||||
type: "object",
|
||||
properties: {
|
||||
before: { type: "boolean" },
|
||||
after: { type: "boolean" }
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
@@ -29,7 +45,10 @@ module.exports = {
|
||||
type: "object",
|
||||
properties: {
|
||||
before: { type: "boolean" },
|
||||
after: { type: "boolean" }
|
||||
after: { type: "boolean" },
|
||||
named: OVERRIDE_SCHEMA,
|
||||
anonymous: OVERRIDE_SCHEMA,
|
||||
method: OVERRIDE_SCHEMA
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
@@ -40,16 +59,39 @@ module.exports = {
|
||||
|
||||
create(context) {
|
||||
|
||||
const mode = (function(option) {
|
||||
if (!option || 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"];
|
||||
const optionDefinitions = {
|
||||
before: { before: true, after: false },
|
||||
after: { before: false, after: true },
|
||||
both: { before: true, after: true },
|
||||
neither: { before: false, after: false }
|
||||
};
|
||||
|
||||
/**
|
||||
* Returns resolved option definitions based on an option and defaults
|
||||
*
|
||||
* @param {any} option - The option object or string value
|
||||
* @param {Object} defaults - The defaults to use if options are not present
|
||||
* @returns {Object} the resolved object definition
|
||||
*/
|
||||
function optionToDefinition(option, defaults) {
|
||||
if (!option) {
|
||||
return defaults;
|
||||
}
|
||||
return option;
|
||||
|
||||
return typeof option === "string"
|
||||
? optionDefinitions[option]
|
||||
: Object.assign({}, defaults, option);
|
||||
}
|
||||
|
||||
const modes = (function(option) {
|
||||
option = option || {};
|
||||
const defaults = optionToDefinition(option, optionDefinitions.before);
|
||||
|
||||
return {
|
||||
named: optionToDefinition(option.named, defaults),
|
||||
anonymous: optionToDefinition(option.anonymous, defaults),
|
||||
method: optionToDefinition(option.method, defaults)
|
||||
};
|
||||
}(context.options[0]));
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
@@ -79,6 +121,8 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Checks the spacing between two tokens before or after the star token.
|
||||
*
|
||||
* @param {string} kind Either "named", "anonymous", or "method"
|
||||
* @param {string} side Either "before" or "after".
|
||||
* @param {Token} leftToken `function` keyword token if side is "before", or
|
||||
* star token if side is "after".
|
||||
@@ -86,10 +130,10 @@ module.exports = {
|
||||
* token if side is "after".
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkSpacing(side, leftToken, rightToken) {
|
||||
if (!!(rightToken.range[0] - leftToken.range[1]) !== mode[side]) {
|
||||
function checkSpacing(kind, side, leftToken, rightToken) {
|
||||
if (!!(rightToken.range[0] - leftToken.range[1]) !== modes[kind][side]) {
|
||||
const after = leftToken.value === "*";
|
||||
const spaceRequired = mode[side];
|
||||
const spaceRequired = modes[kind][side];
|
||||
const node = after ? leftToken : rightToken;
|
||||
const type = spaceRequired ? "Missing" : "Unexpected";
|
||||
const message = "{{type}} space {{side}} *.";
|
||||
@@ -117,6 +161,7 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Enforces the spacing around the star if node is a generator function.
|
||||
*
|
||||
* @param {ASTNode} node A function expression or declaration node.
|
||||
* @returns {void}
|
||||
*/
|
||||
@@ -126,17 +171,23 @@ module.exports = {
|
||||
}
|
||||
|
||||
const starToken = getStarToken(node);
|
||||
|
||||
// Only check before when preceded by `function`|`static` keyword
|
||||
const prevToken = sourceCode.getTokenBefore(starToken);
|
||||
|
||||
if (prevToken.value === "function" || prevToken.value === "static") {
|
||||
checkSpacing("before", prevToken, starToken);
|
||||
}
|
||||
|
||||
const nextToken = sourceCode.getTokenAfter(starToken);
|
||||
|
||||
checkSpacing("after", starToken, nextToken);
|
||||
let kind = "named";
|
||||
|
||||
if (node.parent.type === "MethodDefinition" || (node.parent.type === "Property" && node.parent.method)) {
|
||||
kind = "method";
|
||||
} else if (!node.id) {
|
||||
kind = "anonymous";
|
||||
}
|
||||
|
||||
// Only check before when preceded by `function`|`static` keyword
|
||||
if (!(kind === "method" && starToken === sourceCode.getFirstToken(node.parent))) {
|
||||
checkSpacing(kind, "before", prevToken, starToken);
|
||||
}
|
||||
|
||||
checkSpacing(kind, "after", starToken, nextToken);
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -14,6 +14,7 @@ const astUtils = require("../ast-utils");
|
||||
//------------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//------------------------------------------------------------------------------
|
||||
const TARGET_NODE_TYPE = /^(?:Arrow)?FunctionExpression$/;
|
||||
|
||||
/**
|
||||
* Checks a given code path segment is reachable.
|
||||
@@ -101,22 +102,45 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
/** Checks whether a node means a getter function.
|
||||
* @param {ASTNode} node - a node to check.
|
||||
* @returns {boolean} if node means a getter, return true; else return false.
|
||||
*/
|
||||
function isGetter(node) {
|
||||
const parent = node.parent;
|
||||
|
||||
if (TARGET_NODE_TYPE.test(node.type) && node.body.type === "BlockStatement") {
|
||||
if (parent.kind === "get") {
|
||||
return true;
|
||||
}
|
||||
if (parent.type === "Property" && astUtils.getStaticPropertyName(parent) === "get" && parent.parent.type === "ObjectExpression") {
|
||||
|
||||
// Object.defineProperty()
|
||||
if (parent.parent.parent.type === "CallExpression" &&
|
||||
astUtils.getStaticPropertyName(parent.parent.parent.callee) === "defineProperty") {
|
||||
return true;
|
||||
}
|
||||
|
||||
// Object.defineProperties()
|
||||
if (parent.parent.parent.type === "Property" &&
|
||||
parent.parent.parent.parent.type === "ObjectExpression" &&
|
||||
parent.parent.parent.parent.parent.type === "CallExpression" &&
|
||||
astUtils.getStaticPropertyName(parent.parent.parent.parent.parent.callee) === "defineProperties") {
|
||||
return true;
|
||||
}
|
||||
}
|
||||
}
|
||||
return false;
|
||||
}
|
||||
return {
|
||||
|
||||
// Stacks this function's information.
|
||||
onCodePathStart(codePath, node) {
|
||||
const parent = node.parent;
|
||||
|
||||
funcInfo = {
|
||||
upper: funcInfo,
|
||||
codePath,
|
||||
hasReturn: false,
|
||||
shouldCheck:
|
||||
node.type === "FunctionExpression" &&
|
||||
node.body.type === "BlockStatement" &&
|
||||
|
||||
// check if it is a "getter", or a method named "get".
|
||||
(parent.kind === "get" || astUtils.getStaticPropertyName(parent) === "get"),
|
||||
shouldCheck: isGetter(node),
|
||||
node
|
||||
};
|
||||
},
|
||||
@@ -145,7 +169,8 @@ module.exports = {
|
||||
},
|
||||
|
||||
// Reports a given function if the last path is reachable.
|
||||
"FunctionExpression:exit": checkLastSegment
|
||||
"FunctionExpression:exit": checkLastSegment,
|
||||
"ArrowFunctionExpression:exit": checkLastSegment
|
||||
};
|
||||
}
|
||||
};
|
||||
@@ -67,9 +67,13 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function report(node) {
|
||||
context.report({ node, message: "Identifier '{{name}}' is blacklisted.", data: {
|
||||
name: node.name
|
||||
} });
|
||||
context.report({
|
||||
node,
|
||||
message: "Identifier '{{name}}' is blacklisted.",
|
||||
data: {
|
||||
name: node.name
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -75,10 +75,14 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function report(node) {
|
||||
context.report({ node, message: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.", data: {
|
||||
name: node.name,
|
||||
pattern
|
||||
} });
|
||||
context.report({
|
||||
node,
|
||||
message: "Identifier '{{name}}' does not match the pattern '{{pattern}}'.",
|
||||
data: {
|
||||
name: node.name,
|
||||
pattern
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -729,7 +729,7 @@ module.exports = {
|
||||
if (!parentVarNode || parentVarNode.loc.start.line !== node.loc.start.line) {
|
||||
if (parent.type !== "VariableDeclarator" || parentVarNode === parentVarNode.parent.declarations[0]) {
|
||||
if (parent.type === "VariableDeclarator" && parentVarNode.loc.start.line === parent.loc.start.line) {
|
||||
nodeIndent = nodeIndent + (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
|
||||
nodeIndent += (indentSize * options.VariableDeclarator[parentVarNode.parent.kind]);
|
||||
} else if (parent.type === "ObjectExpression" || parent.type === "ArrayExpression") {
|
||||
const parentElements = node.parent.type === "ObjectExpression" ? node.parent.properties : node.parent.elements;
|
||||
|
||||
@@ -765,7 +765,7 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
} else if (!parentVarNode && !isFirstArrayElementOnSameLine(parent) && parent.type !== "MemberExpression" && parent.type !== "ExpressionStatement" && parent.type !== "AssignmentExpression" && parent.type !== "Property") {
|
||||
nodeIndent = nodeIndent + indentSize;
|
||||
nodeIndent += indentSize;
|
||||
}
|
||||
|
||||
checkFirstNodeLineIndent(node, nodeIndent);
|
||||
@@ -965,7 +965,8 @@ module.exports = {
|
||||
const regex = /^return\s*?\(\s*?\);*?/;
|
||||
|
||||
const statementWithoutArgument = sourceCode.getText(node).replace(
|
||||
sourceCode.getText(node.argument), "");
|
||||
sourceCode.getText(node.argument), ""
|
||||
);
|
||||
|
||||
return regex.test(statementWithoutArgument);
|
||||
}
|
||||
@@ -1039,7 +1040,7 @@ module.exports = {
|
||||
|
||||
const checkNodes = [node.property];
|
||||
|
||||
const dot = context.getTokenBefore(node.property);
|
||||
const dot = sourceCode.getTokenBefore(node.property);
|
||||
|
||||
if (dot.type === "Punctuator" && dot.value === ".") {
|
||||
checkNodes.push(dot);
|
||||
|
||||
@@ -578,8 +578,18 @@ module.exports = {
|
||||
},
|
||||
ArrayExpression: ELEMENT_LIST_SCHEMA,
|
||||
ObjectExpression: ELEMENT_LIST_SCHEMA,
|
||||
ImportDeclaration: ELEMENT_LIST_SCHEMA,
|
||||
flatTernaryExpressions: {
|
||||
type: "boolean"
|
||||
},
|
||||
ignoredNodes: {
|
||||
type: "array",
|
||||
items: {
|
||||
type: "string",
|
||||
not: {
|
||||
pattern: ":exit$"
|
||||
}
|
||||
}
|
||||
}
|
||||
},
|
||||
additionalProperties: false
|
||||
@@ -616,7 +626,9 @@ module.exports = {
|
||||
MemberExpression: 1,
|
||||
ArrayExpression: 1,
|
||||
ObjectExpression: 1,
|
||||
flatTernaryExpressions: false
|
||||
ImportDeclaration: 1,
|
||||
flatTernaryExpressions: false,
|
||||
ignoredNodes: []
|
||||
};
|
||||
|
||||
if (context.options.length) {
|
||||
@@ -781,7 +793,7 @@ module.exports = {
|
||||
offsets.setDesiredOffsets(
|
||||
[startToken.range[1], endToken.range[0]],
|
||||
startToken,
|
||||
offset === "first" ? 1 : offset
|
||||
typeof offset === "number" ? offset : 1
|
||||
);
|
||||
offsets.setDesiredOffset(endToken, startToken, 0);
|
||||
|
||||
@@ -790,10 +802,19 @@ module.exports = {
|
||||
return;
|
||||
}
|
||||
elements.forEach((element, index) => {
|
||||
if (!element) {
|
||||
|
||||
// Skip holes in arrays
|
||||
return;
|
||||
}
|
||||
if (offset === "off") {
|
||||
|
||||
// Ignore the first token of every element if the "off" option is used
|
||||
offsets.ignoreToken(getFirstToken(element));
|
||||
}
|
||||
if (index === 0 || !element) {
|
||||
|
||||
// Offset the following elements correctly relative to the first element
|
||||
if (index === 0) {
|
||||
return;
|
||||
}
|
||||
if (offset === "first" && tokenInfo.isFirstTokenOfLine(getFirstToken(element))) {
|
||||
@@ -912,7 +933,7 @@ module.exports = {
|
||||
* @param {ASTNode} node Unknown Node
|
||||
* @returns {void}
|
||||
*/
|
||||
function ignoreUnknownNode(node) {
|
||||
function ignoreNode(node) {
|
||||
const unknownNodeTokens = new Set(sourceCode.getTokens(node, { includeComments: true }));
|
||||
|
||||
unknownNodeTokens.forEach(token => {
|
||||
@@ -929,25 +950,12 @@ module.exports = {
|
||||
}
|
||||
|
||||
/**
|
||||
* Ignore node if it is unknown
|
||||
* @param {ASTNode} node Node
|
||||
* @returns {void}
|
||||
*/
|
||||
function checkForUnknownNode(node) {
|
||||
const isNodeUnknown = !(KNOWN_NODES.has(node.type));
|
||||
|
||||
if (isNodeUnknown) {
|
||||
ignoreUnknownNode(node);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check whether the given token is the first token of a statement.
|
||||
* Check whether the given token is on the first line of a statement.
|
||||
* @param {Token} token The token to check.
|
||||
* @param {ASTNode} leafNode The expression node that the token belongs directly.
|
||||
* @returns {boolean} `true` if the token is the first token of a statement.
|
||||
* @returns {boolean} `true` if the token is on the first line of a statement.
|
||||
*/
|
||||
function isFirstTokenOfStatement(token, leafNode) {
|
||||
function isOnFirstLineOfStatement(token, leafNode) {
|
||||
let node = leafNode;
|
||||
|
||||
while (node.parent && !node.parent.type.endsWith("Statement") && !node.parent.type.endsWith("Declaration")) {
|
||||
@@ -955,16 +963,25 @@ module.exports = {
|
||||
}
|
||||
node = node.parent;
|
||||
|
||||
return !node || node.range[0] === token.range[0];
|
||||
return !node || node.loc.start.line === token.loc.start.line;
|
||||
}
|
||||
|
||||
return {
|
||||
const baseOffsetListeners = {
|
||||
"ArrayExpression, ArrayPattern"(node) {
|
||||
addElementListIndent(node.elements, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), options.ArrayExpression);
|
||||
const openingBracket = sourceCode.getFirstToken(node);
|
||||
const closingBracket = sourceCode.getTokenAfter(lodash.findLast(node.elements) || openingBracket, astUtils.isClosingBracketToken);
|
||||
|
||||
addElementListIndent(node.elements, openingBracket, closingBracket, options.ArrayExpression);
|
||||
},
|
||||
|
||||
"ObjectExpression, ObjectPattern"(node) {
|
||||
addElementListIndent(node.properties, sourceCode.getFirstToken(node), sourceCode.getLastToken(node), options.ObjectExpression);
|
||||
const openingCurly = sourceCode.getFirstToken(node);
|
||||
const closingCurly = sourceCode.getTokenAfter(
|
||||
node.properties.length ? node.properties[node.properties.length - 1] : openingCurly,
|
||||
astUtils.isClosingBraceToken
|
||||
);
|
||||
|
||||
addElementListIndent(node.properties, openingCurly, closingCurly, options.ObjectExpression);
|
||||
},
|
||||
|
||||
ArrowFunctionExpression(node) {
|
||||
@@ -1059,7 +1076,7 @@ module.exports = {
|
||||
// /*else*/ qiz ;
|
||||
if (!options.flatTernaryExpressions ||
|
||||
!astUtils.isTokenOnSameLine(node.test, node.consequent) ||
|
||||
isFirstTokenOfStatement(firstToken, node)
|
||||
isOnFirstLineOfStatement(firstToken, node)
|
||||
) {
|
||||
const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?");
|
||||
const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":");
|
||||
@@ -1157,7 +1174,7 @@ module.exports = {
|
||||
const openingCurly = sourceCode.getFirstToken(node, astUtils.isOpeningBraceToken);
|
||||
const closingCurly = sourceCode.getLastToken(node, astUtils.isClosingBraceToken);
|
||||
|
||||
addElementListIndent(node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"), openingCurly, closingCurly, 1);
|
||||
addElementListIndent(node.specifiers.filter(specifier => specifier.type === "ImportSpecifier"), openingCurly, closingCurly, options.ImportDeclaration);
|
||||
}
|
||||
|
||||
const fromToken = sourceCode.getLastToken(node, token => token.type === "Identifier" && token.value === "from");
|
||||
@@ -1167,14 +1184,15 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
"MemberExpression, JSXMemberExpression"(node) {
|
||||
const firstNonObjectToken = sourceCode.getFirstTokenBetween(node.object, node.property, astUtils.isNotClosingParenToken);
|
||||
"MemberExpression, JSXMemberExpression, MetaProperty"(node) {
|
||||
const object = node.type === "MetaProperty" ? node.meta : node.object;
|
||||
const firstNonObjectToken = sourceCode.getFirstTokenBetween(object, node.property, astUtils.isNotClosingParenToken);
|
||||
const secondNonObjectToken = sourceCode.getTokenAfter(firstNonObjectToken);
|
||||
|
||||
const objectParenCount = sourceCode.getTokensBetween(node.object, node.property, { filter: astUtils.isClosingParenToken }).length;
|
||||
const objectParenCount = sourceCode.getTokensBetween(object, node.property, { filter: astUtils.isClosingParenToken }).length;
|
||||
const firstObjectToken = objectParenCount
|
||||
? sourceCode.getTokenBefore(node.object, { skip: objectParenCount - 1 })
|
||||
: sourceCode.getFirstToken(node.object);
|
||||
? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 })
|
||||
: sourceCode.getFirstToken(object);
|
||||
const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken);
|
||||
const firstPropertyToken = node.computed ? firstNonObjectToken : secondNonObjectToken;
|
||||
|
||||
@@ -1229,7 +1247,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
Property(node) {
|
||||
if (!node.computed && !node.shorthand && !node.method && node.kind === "init") {
|
||||
if (!node.shorthand && !node.method && node.kind === "init") {
|
||||
const colon = sourceCode.getFirstTokenBetween(node.key, node.value, astUtils.isColonToken);
|
||||
|
||||
offsets.ignoreToken(sourceCode.getTokenAfter(colon));
|
||||
@@ -1322,8 +1340,6 @@ module.exports = {
|
||||
}
|
||||
},
|
||||
|
||||
"*:exit": checkForUnknownNode,
|
||||
|
||||
"JSXAttribute[value]"(node) {
|
||||
const equalsToken = sourceCode.getFirstTokenBetween(node.name, node.value, token => token.type === "Punctuator" && token.value === "=");
|
||||
|
||||
@@ -1367,62 +1383,130 @@ module.exports = {
|
||||
1
|
||||
);
|
||||
offsets.setDesiredOffset(closingCurly, openingCurly, 0);
|
||||
},
|
||||
|
||||
"Program:exit"() {
|
||||
addParensIndent(sourceCode.ast.tokens);
|
||||
|
||||
/*
|
||||
* Create a Map from (tokenOrComment) => (precedingToken).
|
||||
* This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
|
||||
*/
|
||||
const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => {
|
||||
const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
|
||||
|
||||
return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore);
|
||||
}, new WeakMap());
|
||||
|
||||
sourceCode.lines.forEach((line, lineIndex) => {
|
||||
const lineNumber = lineIndex + 1;
|
||||
|
||||
if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) {
|
||||
|
||||
// Don't check indentation on blank lines
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber);
|
||||
|
||||
if (firstTokenOfLine.loc.start.line !== lineNumber) {
|
||||
|
||||
// Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the token matches the expected expected indentation, don't report it.
|
||||
if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (astUtils.isCommentToken(firstTokenOfLine)) {
|
||||
const tokenBefore = precedingTokens.get(firstTokenOfLine);
|
||||
const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0];
|
||||
|
||||
// If a comment matches the expected indentation of the token immediately before or after, don't report it.
|
||||
if (
|
||||
tokenBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
|
||||
tokenAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, report the token/comment.
|
||||
report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine));
|
||||
});
|
||||
}
|
||||
|
||||
};
|
||||
|
||||
const listenerCallQueue = [];
|
||||
|
||||
/*
|
||||
* To ignore the indentation of a node:
|
||||
* 1. Don't call the node's listener when entering it (if it has a listener)
|
||||
* 2. Call `ignoreNode` on the node sometime after exiting it and before validating offsets.
|
||||
*/
|
||||
const offsetListeners = lodash.mapValues(
|
||||
baseOffsetListeners,
|
||||
|
||||
/*
|
||||
* Offset listener calls are deferred until traversal is finished, and are called as
|
||||
* part of the final `Program:exit` listener. This is necessary because a node might
|
||||
* be matched by multiple selectors.
|
||||
*
|
||||
* Example: Suppose there is an offset listener for `Identifier`, and the user has
|
||||
* specified in configuration that `MemberExpression > Identifier` should be ignored.
|
||||
* Due to selector specificity rules, the `Identifier` listener will get called first. However,
|
||||
* if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener
|
||||
* should not have been called at all. Without doing extra selector matching, we don't know
|
||||
* whether the Identifier matches the `MemberExpression > Identifier` selector until the
|
||||
* `MemberExpression > Identifier` listener is called.
|
||||
*
|
||||
* To avoid this, the `Identifier` listener isn't called until traversal finishes and all
|
||||
* ignored nodes are known.
|
||||
*/
|
||||
listener =>
|
||||
node =>
|
||||
listenerCallQueue.push({ listener, node })
|
||||
);
|
||||
|
||||
// For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set.
|
||||
const ignoredNodes = new Set();
|
||||
const addToIgnoredNodes = ignoredNodes.add.bind(ignoredNodes);
|
||||
|
||||
const ignoredNodeListeners = options.ignoredNodes.reduce(
|
||||
(listeners, ignoredSelector) => Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }),
|
||||
{}
|
||||
);
|
||||
|
||||
/*
|
||||
* Join the listeners, and add a listener to verify that all tokens actually have the correct indentation
|
||||
* at the end.
|
||||
*
|
||||
* Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears
|
||||
* in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored,
|
||||
* so those listeners wouldn't be called anyway.
|
||||
*/
|
||||
return Object.assign(
|
||||
offsetListeners,
|
||||
ignoredNodeListeners,
|
||||
{
|
||||
"*:exit"(node) {
|
||||
|
||||
// If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it.
|
||||
if (!KNOWN_NODES.has(node.type)) {
|
||||
ignoredNodes.add(node);
|
||||
}
|
||||
},
|
||||
"Program:exit"() {
|
||||
|
||||
// Invoke the queued offset listeners for the nodes that aren't ignored.
|
||||
listenerCallQueue
|
||||
.filter(nodeInfo => !ignoredNodes.has(nodeInfo.node))
|
||||
.forEach(nodeInfo => nodeInfo.listener(nodeInfo.node));
|
||||
|
||||
// Update the offsets for ignored nodes to prevent their child tokens from being reported.
|
||||
ignoredNodes.forEach(ignoreNode);
|
||||
|
||||
addParensIndent(sourceCode.ast.tokens);
|
||||
|
||||
/*
|
||||
* Create a Map from (tokenOrComment) => (precedingToken).
|
||||
* This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
|
||||
*/
|
||||
const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => {
|
||||
const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
|
||||
|
||||
return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore);
|
||||
}, new WeakMap());
|
||||
|
||||
sourceCode.lines.forEach((line, lineIndex) => {
|
||||
const lineNumber = lineIndex + 1;
|
||||
|
||||
if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) {
|
||||
|
||||
// Don't check indentation on blank lines
|
||||
return;
|
||||
}
|
||||
|
||||
const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get(lineNumber);
|
||||
|
||||
if (firstTokenOfLine.loc.start.line !== lineNumber) {
|
||||
|
||||
// Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice.
|
||||
return;
|
||||
}
|
||||
|
||||
// If the token matches the expected expected indentation, don't report it.
|
||||
if (validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (astUtils.isCommentToken(firstTokenOfLine)) {
|
||||
const tokenBefore = precedingTokens.get(firstTokenOfLine);
|
||||
const tokenAfter = tokenBefore ? sourceCode.getTokenAfter(tokenBefore) : sourceCode.ast.tokens[0];
|
||||
|
||||
// If a comment matches the expected indentation of the token immediately before or after, don't report it.
|
||||
if (
|
||||
tokenBefore && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenBefore)) ||
|
||||
tokenAfter && validateTokenIndent(firstTokenOfLine, offsets.getDesiredIndent(tokenAfter))
|
||||
) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
// Otherwise, report the token/comment.
|
||||
report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine));
|
||||
});
|
||||
}
|
||||
}
|
||||
);
|
||||
}
|
||||
};
|
||||
@@ -420,7 +420,6 @@ module.exports = {
|
||||
isExtra = diff > 0,
|
||||
diffAbs = Math.abs(diff),
|
||||
spaces = Array(diffAbs + 1).join(" ");
|
||||
let fix;
|
||||
|
||||
if ((
|
||||
diff && mode === "strict" ||
|
||||
@@ -428,14 +427,16 @@ module.exports = {
|
||||
diff > 0 && !expected && mode === "minimum") &&
|
||||
!(expected && containsLineTerminator(whitespace))
|
||||
) {
|
||||
let fix;
|
||||
|
||||
if (isExtra) {
|
||||
let range;
|
||||
|
||||
// Remove whitespace
|
||||
if (isKeySide) {
|
||||
range = [tokenBeforeColon.end, tokenBeforeColon.end + diffAbs];
|
||||
range = [tokenBeforeColon.range[1], tokenBeforeColon.range[1] + diffAbs];
|
||||
} else {
|
||||
range = [tokenAfterColon.start - diffAbs, tokenAfterColon.start];
|
||||
range = [tokenAfterColon.range[0] - diffAbs, tokenAfterColon.range[0]];
|
||||
}
|
||||
fix = function(fixer) {
|
||||
return fixer.removeRange(range);
|
||||
|
||||
@@ -181,11 +181,10 @@ module.exports = {
|
||||
* Gets the line after the comment and any remaining trailing whitespace is
|
||||
* stripped.
|
||||
* @param {string} line The source line with a trailing comment
|
||||
* @param {number} lineNumber The one-indexed line number this is on
|
||||
* @param {ASTNode} comment The comment to remove
|
||||
* @returns {string} Line without comment and trailing whitepace
|
||||
*/
|
||||
function stripTrailingComment(line, lineNumber, comment) {
|
||||
function stripTrailingComment(line, comment) {
|
||||
|
||||
// loc.column is zero-indexed
|
||||
return line.slice(0, comment.loc.start.column).replace(/\s+$/, "");
|
||||
@@ -306,7 +305,7 @@ module.exports = {
|
||||
if (isFullLineComment(line, lineNumber, comment)) {
|
||||
lineIsComment = true;
|
||||
} else if (ignoreTrailingComments && isTrailingComment(line, lineNumber, comment)) {
|
||||
line = stripTrailingComment(line, lineNumber, comment);
|
||||
line = stripTrailingComment(line, comment);
|
||||
}
|
||||
}
|
||||
if (ignorePattern && ignorePattern.test(line) ||
|
||||
|
||||
@@ -19,7 +19,7 @@ module.exports = {
|
||||
category: "Stylistic Issues",
|
||||
recommended: false
|
||||
},
|
||||
|
||||
fixable: "whitespace",
|
||||
schema: [{
|
||||
type: "object",
|
||||
properties: {
|
||||
@@ -40,6 +40,18 @@ module.exports = {
|
||||
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
/**
|
||||
* Get the prefix of a given MemberExpression node.
|
||||
* If the MemberExpression node is a computed value it returns a
|
||||
* left bracket. If not it returns a period.
|
||||
*
|
||||
* @param {ASTNode} node - A MemberExpression node to get
|
||||
* @returns {string} The prefix of the node.
|
||||
*/
|
||||
function getPrefix(node) {
|
||||
return node.computed ? "[" : ".";
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the property text of a given MemberExpression node.
|
||||
* If the text is multiline, this returns only the first line.
|
||||
@@ -48,7 +60,7 @@ module.exports = {
|
||||
* @returns {string} The property text of the node.
|
||||
*/
|
||||
function getPropertyText(node) {
|
||||
const prefix = node.computed ? "[" : ".";
|
||||
const prefix = getPrefix(node);
|
||||
const lines = sourceCode.getText(node.property).split(astUtils.LINEBREAK_MATCHER);
|
||||
const suffix = node.computed && lines.length === 1 ? "]" : "";
|
||||
|
||||
@@ -70,13 +82,18 @@ module.exports = {
|
||||
parent = parent.callee.object;
|
||||
}
|
||||
|
||||
if (depth > ignoreChainWithDepth && callee.property.loc.start.line === callee.object.loc.end.line) {
|
||||
if (depth > ignoreChainWithDepth && astUtils.isTokenOnSameLine(callee.object, callee.property)) {
|
||||
context.report({
|
||||
node: callee.property,
|
||||
loc: callee.property.loc.start,
|
||||
message: "Expected line break before `{{callee}}`.",
|
||||
data: {
|
||||
callee: getPropertyText(callee)
|
||||
},
|
||||
fix(fixer) {
|
||||
const firstTokenAfterObject = sourceCode.getTokenAfter(callee.object, astUtils.isNotClosingParenToken);
|
||||
|
||||
return fixer.insertTextBefore(firstTokenAfterObject, "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -53,11 +53,10 @@ function findReference(scope, node) {
|
||||
/**
|
||||
* 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) {
|
||||
function isShadowed(scope, node) {
|
||||
const reference = findReference(scope, node);
|
||||
|
||||
return reference && reference.resolved && reference.resolved.defs.length > 0;
|
||||
@@ -66,15 +65,14 @@ function isShadowed(scope, globalScope, node) {
|
||||
/**
|
||||
* 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) {
|
||||
function isGlobalThisReferenceOrGlobalWindow(scope, node) {
|
||||
if (scope.type === "global" && node.type === "ThisExpression") {
|
||||
return true;
|
||||
} else if (node.name === "window") {
|
||||
return !isShadowed(scope, globalScope, node);
|
||||
return !isShadowed(scope, node);
|
||||
}
|
||||
|
||||
return false;
|
||||
@@ -96,14 +94,7 @@ module.exports = {
|
||||
},
|
||||
|
||||
create(context) {
|
||||
let globalScope;
|
||||
|
||||
return {
|
||||
|
||||
Program() {
|
||||
globalScope = context.getScope();
|
||||
},
|
||||
|
||||
CallExpression(node) {
|
||||
const callee = node.callee,
|
||||
currentScope = context.getScope();
|
||||
@@ -112,11 +103,11 @@ module.exports = {
|
||||
if (callee.type === "Identifier") {
|
||||
const identifierName = callee.name;
|
||||
|
||||
if (!isShadowed(currentScope, globalScope, callee) && isProhibitedIdentifier(callee.name)) {
|
||||
if (!isShadowed(currentScope, callee) && isProhibitedIdentifier(callee.name)) {
|
||||
report(context, node, identifierName);
|
||||
}
|
||||
|
||||
} else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, globalScope, callee.object)) {
|
||||
} else if (callee.type === "MemberExpression" && isGlobalThisReferenceOrGlobalWindow(currentScope, callee.object)) {
|
||||
const identifierName = getPropertyName(callee);
|
||||
|
||||
if (isProhibitedIdentifier(identifierName)) {
|
||||
|
||||
@@ -112,9 +112,13 @@ module.exports = {
|
||||
const ancestor = findConditionalAncestor(node);
|
||||
|
||||
if (ancestor) {
|
||||
context.report({ node: ancestor, message: "Unexpected assignment within {{type}}.", data: {
|
||||
type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
|
||||
} });
|
||||
context.report({
|
||||
node: ancestor,
|
||||
message: "Unexpected assignment within {{type}}.",
|
||||
data: {
|
||||
type: NODE_DESCRIPTIONS[ancestor.type] || ancestor.type
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -99,7 +99,7 @@ module.exports = {
|
||||
// https://github.com/eslint/eslint/issues/8026
|
||||
return new FixTracker(fixer, sourceCode)
|
||||
.retainEnclosingFunction(node)
|
||||
.replaceTextRange([elseToken.start, node.end], fixedSource);
|
||||
.replaceTextRange([elseToken.range[0], node.range[1]], fixedSource);
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -276,6 +276,15 @@ module.exports = {
|
||||
!astUtils.canTokensBeAdjacent(tokenBeforeRightParen, tokenAfterRightParen);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given expression node is an IIFE
|
||||
* @param {ASTNode} node The node to check
|
||||
* @returns {boolean} `true` if the given node is an IIFE
|
||||
*/
|
||||
function isIIFE(node) {
|
||||
return node.type === "CallExpression" && node.callee.type === "FunctionExpression";
|
||||
}
|
||||
|
||||
/**
|
||||
* Report the node
|
||||
* @param {ASTNode} node node to evaluate
|
||||
@@ -286,8 +295,14 @@ module.exports = {
|
||||
const leftParenToken = sourceCode.getTokenBefore(node);
|
||||
const rightParenToken = sourceCode.getTokenAfter(node);
|
||||
|
||||
if (tokensToIgnore.has(sourceCode.getFirstToken(node)) && !isParenthesisedTwice(node)) {
|
||||
return;
|
||||
if (!isParenthesisedTwice(node)) {
|
||||
if (tokensToIgnore.has(sourceCode.getFirstToken(node))) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (isIIFE(node) && !isParenthesised(node.callee)) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
context.report({
|
||||
@@ -321,6 +336,23 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Check if a member expression contains a call expression
|
||||
* @param {ASTNode} node MemberExpression node to evaluate
|
||||
* @returns {boolean} true if found, false if not
|
||||
*/
|
||||
function doesMemberExpressionContainCallExpression(node) {
|
||||
let currentNode = node.object;
|
||||
let currentNodeType = node.object.type;
|
||||
|
||||
while (currentNodeType === "MemberExpression") {
|
||||
currentNode = currentNode.object;
|
||||
currentNodeType = currentNode.type;
|
||||
}
|
||||
|
||||
return currentNodeType === "CallExpression";
|
||||
}
|
||||
|
||||
/**
|
||||
* Evaluate a new call
|
||||
* @param {ASTNode} node node to evaluate
|
||||
@@ -328,26 +360,32 @@ module.exports = {
|
||||
* @private
|
||||
*/
|
||||
function checkCallNew(node) {
|
||||
if (hasExcessParens(node.callee) && precedence(node.callee) >= precedence(node) && !(
|
||||
node.type === "CallExpression" &&
|
||||
(node.callee.type === "FunctionExpression" ||
|
||||
node.callee.type === "NewExpression" && !isNewExpressionWithParens(node.callee)) &&
|
||||
const callee = node.callee;
|
||||
|
||||
// One set of parentheses are allowed for a function expression
|
||||
!hasDoubleExcessParens(node.callee)
|
||||
)) {
|
||||
report(node.callee);
|
||||
if (hasExcessParens(callee) && precedence(callee) >= precedence(node)) {
|
||||
const hasNewParensException = callee.type === "NewExpression" && !isNewExpressionWithParens(callee);
|
||||
|
||||
if (
|
||||
hasDoubleExcessParens(callee) ||
|
||||
!isIIFE(node) && !hasNewParensException && !(
|
||||
|
||||
// Allow extra parens around a new expression if
|
||||
// there are intervening parentheses.
|
||||
callee.type === "MemberExpression" &&
|
||||
doesMemberExpressionContainCallExpression(callee)
|
||||
)
|
||||
) {
|
||||
report(node.callee);
|
||||
}
|
||||
}
|
||||
if (node.arguments.length === 1) {
|
||||
if (hasDoubleExcessParens(node.arguments[0]) && precedence(node.arguments[0]) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(node.arguments[0]);
|
||||
}
|
||||
} else {
|
||||
[].forEach.call(node.arguments, arg => {
|
||||
if (hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(arg);
|
||||
}
|
||||
});
|
||||
node.arguments
|
||||
.filter(arg => hasExcessParens(arg) && precedence(arg) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
|
||||
.forEach(report);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -418,6 +456,7 @@ module.exports = {
|
||||
function checkExpressionOrExportStatement(node) {
|
||||
const firstToken = isParenthesised(node) ? sourceCode.getTokenBefore(node) : sourceCode.getFirstToken(node);
|
||||
const secondToken = sourceCode.getTokenAfter(firstToken, astUtils.isNotOpeningParenToken);
|
||||
const thirdToken = secondToken ? sourceCode.getTokenAfter(secondToken) : null;
|
||||
|
||||
if (
|
||||
astUtils.isOpeningParenToken(firstToken) &&
|
||||
@@ -427,7 +466,8 @@ module.exports = {
|
||||
secondToken.value === "function" ||
|
||||
secondToken.value === "class" ||
|
||||
secondToken.value === "let" && astUtils.isOpeningBracketToken(sourceCode.getTokenAfter(secondToken, astUtils.isNotClosingParenToken))
|
||||
)
|
||||
) ||
|
||||
secondToken && secondToken.type === "Identifier" && secondToken.value === "async" && thirdToken && thirdToken.type === "Keyword" && thirdToken.value === "function"
|
||||
)
|
||||
) {
|
||||
tokensToIgnore.add(secondToken);
|
||||
@@ -440,11 +480,9 @@ module.exports = {
|
||||
|
||||
return {
|
||||
ArrayExpression(node) {
|
||||
[].forEach.call(node.elements, e => {
|
||||
if (e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(e);
|
||||
}
|
||||
});
|
||||
node.elements
|
||||
.filter(e => e && hasExcessParens(e) && precedence(e) >= PRECEDENCE_OF_ASSIGNMENT_EXPR)
|
||||
.forEach(report);
|
||||
},
|
||||
|
||||
ArrowFunctionExpression(node) {
|
||||
@@ -564,8 +602,10 @@ module.exports = {
|
||||
LogicalExpression: checkBinaryLogical,
|
||||
|
||||
MemberExpression(node) {
|
||||
const nodeObjHasExcessParens = hasExcessParens(node.object);
|
||||
|
||||
if (
|
||||
hasExcessParens(node.object) &&
|
||||
nodeObjHasExcessParens &&
|
||||
precedence(node.object) >= precedence(node) &&
|
||||
(
|
||||
node.computed ||
|
||||
@@ -579,6 +619,13 @@ module.exports = {
|
||||
) {
|
||||
report(node.object);
|
||||
}
|
||||
|
||||
if (nodeObjHasExcessParens &&
|
||||
node.object.type === "CallExpression" &&
|
||||
node.parent.type !== "NewExpression") {
|
||||
report(node.object);
|
||||
}
|
||||
|
||||
if (node.computed && hasExcessParens(node.property)) {
|
||||
report(node.property);
|
||||
}
|
||||
@@ -587,13 +634,12 @@ module.exports = {
|
||||
NewExpression: checkCallNew,
|
||||
|
||||
ObjectExpression(node) {
|
||||
[].forEach.call(node.properties, e => {
|
||||
const v = e.value;
|
||||
node.properties
|
||||
.filter(property => {
|
||||
const value = property.value;
|
||||
|
||||
if (v && hasExcessParens(v) && precedence(v) >= PRECEDENCE_OF_ASSIGNMENT_EXPR) {
|
||||
report(v);
|
||||
}
|
||||
});
|
||||
return value && hasExcessParens(value) && precedence(value) >= PRECEDENCE_OF_ASSIGNMENT_EXPR;
|
||||
}).forEach(property => report(property.value));
|
||||
},
|
||||
|
||||
ReturnStatement(node) {
|
||||
@@ -613,11 +659,9 @@ module.exports = {
|
||||
},
|
||||
|
||||
SequenceExpression(node) {
|
||||
[].forEach.call(node.expressions, e => {
|
||||
if (hasExcessParens(e) && precedence(e) >= precedence(node)) {
|
||||
report(e);
|
||||
}
|
||||
});
|
||||
node.expressions
|
||||
.filter(e => hasExcessParens(e) && precedence(e) >= precedence(node))
|
||||
.forEach(report);
|
||||
},
|
||||
|
||||
SwitchCase(node) {
|
||||
|
||||
@@ -63,10 +63,14 @@ module.exports = {
|
||||
body.distance === 2);
|
||||
|
||||
if (!valid) {
|
||||
context.report({ node, message: "Move {{type}} declaration to {{body}} root.", data: {
|
||||
type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
|
||||
body: (body.type === "Program" ? "program" : "function body")
|
||||
} });
|
||||
context.report({
|
||||
node,
|
||||
message: "Move {{type}} declaration to {{body}} root.",
|
||||
data: {
|
||||
type: (node.type === "FunctionDeclaration" ? "function" : "variable"),
|
||||
body: (body.type === "Program" ? "program" : "function body")
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -46,7 +46,8 @@ module.exports = {
|
||||
current.init = true;
|
||||
current.valid = !astUtils.isDefaultThisBinding(
|
||||
current.node,
|
||||
sourceCode);
|
||||
sourceCode
|
||||
);
|
||||
}
|
||||
return current;
|
||||
};
|
||||
|
||||
@@ -88,12 +88,11 @@ function getTopLoopNode(node, excludedNode) {
|
||||
* Checks whether a given reference which refers to an upper scope's variable is
|
||||
* safe or not.
|
||||
*
|
||||
* @param {ASTNode} funcNode - A target function node.
|
||||
* @param {ASTNode} loopNode - A containing loop node.
|
||||
* @param {eslint-scope.Reference} reference - A reference to check.
|
||||
* @returns {boolean} `true` if the reference is safe or not.
|
||||
*/
|
||||
function isSafe(funcNode, loopNode, reference) {
|
||||
function isSafe(loopNode, reference) {
|
||||
const variable = reference.resolved;
|
||||
const definition = variable && variable.defs[0];
|
||||
const declaration = definition && definition.parent;
|
||||
@@ -183,7 +182,7 @@ module.exports = {
|
||||
const references = context.getScope().through;
|
||||
|
||||
if (references.length > 0 &&
|
||||
!references.every(isSafe.bind(null, node, loopNode))
|
||||
!references.every(isSafe.bind(null, loopNode))
|
||||
) {
|
||||
context.report({ node, message: "Don't make functions within a loop." });
|
||||
}
|
||||
|
||||
@@ -44,68 +44,11 @@ module.exports = {
|
||||
},
|
||||
|
||||
create(context) {
|
||||
|
||||
// the index of the last comment that was checked
|
||||
const sourceCode = context.getSourceCode(),
|
||||
exceptions = { Property: true },
|
||||
options = context.options[0] || {},
|
||||
ignoreEOLComments = options.ignoreEOLComments;
|
||||
let hasExceptions = true,
|
||||
lastCommentIndex = 0;
|
||||
|
||||
if (options && options.exceptions) {
|
||||
Object.keys(options.exceptions).forEach(key => {
|
||||
if (options.exceptions[key]) {
|
||||
exceptions[key] = true;
|
||||
} else {
|
||||
delete exceptions[key];
|
||||
}
|
||||
});
|
||||
hasExceptions = Object.keys(exceptions).length > 0;
|
||||
}
|
||||
|
||||
/**
|
||||
* Checks if a given token is the last token of the line or not.
|
||||
* @param {Token} token The token to check.
|
||||
* @returns {boolean} Whether or not a token is at the end of the line it occurs in.
|
||||
* @private
|
||||
*/
|
||||
function isLastTokenOfLine(token) {
|
||||
const nextToken = sourceCode.getTokenAfter(token, { includeComments: true });
|
||||
|
||||
// nextToken is null if the comment is the last token in the program.
|
||||
if (!nextToken) {
|
||||
return true;
|
||||
}
|
||||
|
||||
return !astUtils.isTokenOnSameLine(token, nextToken);
|
||||
}
|
||||
|
||||
/**
|
||||
* Determines if a given source index is in a comment or not by checking
|
||||
* the index against the comment range. Since the check goes straight
|
||||
* through the file, once an index is passed a certain comment, we can
|
||||
* go to the next comment to check that.
|
||||
* @param {int} index The source index to check.
|
||||
* @param {ASTNode[]} comments An array of comment nodes.
|
||||
* @returns {boolean} True if the index is within a comment, false if not.
|
||||
* @private
|
||||
*/
|
||||
function isIndexInComment(index, comments) {
|
||||
while (lastCommentIndex < comments.length) {
|
||||
const comment = comments[lastCommentIndex];
|
||||
|
||||
if (comment.range[0] < index && index < comment.range[1]) {
|
||||
return true;
|
||||
} else if (index > comment.range[1]) {
|
||||
lastCommentIndex++;
|
||||
} else {
|
||||
break;
|
||||
}
|
||||
}
|
||||
|
||||
return false;
|
||||
}
|
||||
const sourceCode = context.getSourceCode();
|
||||
const options = context.options[0] || {};
|
||||
const ignoreEOLComments = options.ignoreEOLComments;
|
||||
const exceptions = Object.assign({ Property: true }, options.exceptions);
|
||||
const hasExceptions = Object.keys(exceptions).filter(key => exceptions[key]).length > 0;
|
||||
|
||||
/**
|
||||
* Formats value of given comment token for error message by truncating its length.
|
||||
@@ -116,75 +59,70 @@ module.exports = {
|
||||
function formatReportedCommentValue(token) {
|
||||
const valueLines = token.value.split("\n");
|
||||
const value = valueLines[0];
|
||||
const formattedValue = `${value.substring(0, 12)}...`;
|
||||
const formattedValue = `${value.slice(0, 12)}...`;
|
||||
|
||||
return valueLines.length === 1 && value.length <= 12 ? value : formattedValue;
|
||||
}
|
||||
|
||||
/**
|
||||
* Creates a fix function that removes the multiple spaces between the two tokens
|
||||
* @param {Token} leftToken left token
|
||||
* @param {Token} rightToken right token
|
||||
* @returns {Function} fix function
|
||||
* @private
|
||||
*/
|
||||
function createFix(leftToken, rightToken) {
|
||||
return function(fixer) {
|
||||
return fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ");
|
||||
};
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Public
|
||||
//--------------------------------------------------------------------------
|
||||
|
||||
return {
|
||||
Program() {
|
||||
|
||||
const source = sourceCode.getText(),
|
||||
allComments = sourceCode.getAllComments(),
|
||||
pattern = /[^\s].*? {2,}/g;
|
||||
let parent;
|
||||
|
||||
while (pattern.test(source)) {
|
||||
|
||||
// do not flag anything inside of comments
|
||||
if (!isIndexInComment(pattern.lastIndex, allComments)) {
|
||||
|
||||
const token = sourceCode.getTokenByRangeStart(pattern.lastIndex, { includeComments: true });
|
||||
|
||||
if (token) {
|
||||
if (ignoreEOLComments && astUtils.isCommentToken(token) && isLastTokenOfLine(token)) {
|
||||
return;
|
||||
}
|
||||
|
||||
const previousToken = sourceCode.getTokenBefore(token, { includeComments: true });
|
||||
|
||||
if (hasExceptions) {
|
||||
parent = sourceCode.getNodeByRangeIndex(pattern.lastIndex - 1);
|
||||
}
|
||||
|
||||
if (!parent || !exceptions[parent.type]) {
|
||||
let value = token.value;
|
||||
|
||||
if (token.type === "Block") {
|
||||
value = `/*${formatReportedCommentValue(token)}*/`;
|
||||
} else if (token.type === "Line") {
|
||||
value = `//${formatReportedCommentValue(token)}`;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: token,
|
||||
loc: token.loc.start,
|
||||
message: "Multiple spaces found before '{{value}}'.",
|
||||
data: { value },
|
||||
fix: createFix(previousToken, token)
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
sourceCode.tokensAndComments.forEach((leftToken, leftIndex, tokensAndComments) => {
|
||||
if (leftIndex === tokensAndComments.length - 1) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
const rightToken = tokensAndComments[leftIndex + 1];
|
||||
|
||||
// Ignore tokens that don't have 2 spaces between them or are on different lines
|
||||
if (
|
||||
!sourceCode.text.slice(leftToken.range[1], rightToken.range[0]).includes(" ") ||
|
||||
leftToken.loc.end.line < rightToken.loc.start.line
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore comments that are the last token on their line if `ignoreEOLComments` is active.
|
||||
if (
|
||||
ignoreEOLComments &&
|
||||
astUtils.isCommentToken(rightToken) &&
|
||||
(
|
||||
leftIndex === tokensAndComments.length - 2 ||
|
||||
rightToken.loc.end.line < tokensAndComments[leftIndex + 2].loc.start.line
|
||||
)
|
||||
) {
|
||||
return;
|
||||
}
|
||||
|
||||
// Ignore tokens that are in a node in the "exceptions" object
|
||||
if (hasExceptions) {
|
||||
const parentNode = sourceCode.getNodeByRangeIndex(rightToken.range[0] - 1);
|
||||
|
||||
if (parentNode && exceptions[parentNode.type]) {
|
||||
return;
|
||||
}
|
||||
}
|
||||
|
||||
let displayValue;
|
||||
|
||||
if (rightToken.type === "Block") {
|
||||
displayValue = `/*${formatReportedCommentValue(rightToken)}*/`;
|
||||
} else if (rightToken.type === "Line") {
|
||||
displayValue = `//${formatReportedCommentValue(rightToken)}`;
|
||||
} else {
|
||||
displayValue = rightToken.value;
|
||||
}
|
||||
|
||||
context.report({
|
||||
node: rightToken,
|
||||
loc: rightToken.loc.start,
|
||||
message: "Multiple spaces found before '{{displayValue}}'.",
|
||||
data: { displayValue },
|
||||
fix: fixer => fixer.replaceTextRange([leftToken.range[1], rightToken.range[0]], " ")
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
@@ -74,7 +74,7 @@ module.exports = {
|
||||
nodeValue = token.value;
|
||||
|
||||
if (nodeType === "RegularExpression") {
|
||||
checkRegex(node, nodeValue, token.start);
|
||||
checkRegex(node, nodeValue, token.range[0]);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -100,7 +100,7 @@ module.exports = {
|
||||
const shadowed = regExpVar && regExpVar.defs.length > 0;
|
||||
|
||||
if (node.callee.type === "Identifier" && node.callee.name === "RegExp" && isString(node.arguments[0]) && !shadowed) {
|
||||
checkRegex(node, node.arguments[0].value, node.arguments[0].start + 1);
|
||||
checkRegex(node, node.arguments[0].value, node.arguments[0].range[0] + 1);
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -109,20 +109,28 @@ module.exports = {
|
||||
if (matchedObjectProperty) {
|
||||
const message = matchedObjectProperty.message ? ` ${matchedObjectProperty.message}` : "";
|
||||
|
||||
// eslint-disable-next-line eslint-plugin/report-message-format
|
||||
context.report({ node, message: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}", data: {
|
||||
objectName,
|
||||
propertyName,
|
||||
message
|
||||
} });
|
||||
context.report({
|
||||
node,
|
||||
// eslint-disable-next-line eslint-plugin/report-message-format
|
||||
message: "'{{objectName}}.{{propertyName}}' is restricted from being used.{{message}}",
|
||||
data: {
|
||||
objectName,
|
||||
propertyName,
|
||||
message
|
||||
}
|
||||
});
|
||||
} else if (globalMatchedProperty) {
|
||||
const message = globalMatchedProperty.message ? ` ${globalMatchedProperty.message}` : "";
|
||||
|
||||
// eslint-disable-next-line eslint-plugin/report-message-format
|
||||
context.report({ node, message: "'{{propertyName}}' is restricted from being used.{{message}}", data: {
|
||||
propertyName,
|
||||
message
|
||||
} });
|
||||
context.report({
|
||||
node,
|
||||
// eslint-disable-next-line eslint-plugin/report-message-format
|
||||
message: "'{{propertyName}}' is restricted from being used.{{message}}",
|
||||
data: {
|
||||
propertyName,
|
||||
message
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -27,14 +27,18 @@ module.exports = {
|
||||
create(context) {
|
||||
return {
|
||||
Program(node) {
|
||||
context.getSourceLines().forEach((line, index) => {
|
||||
context.getSourceCode().getLines().forEach((line, index) => {
|
||||
const match = regex.exec(line);
|
||||
|
||||
if (match) {
|
||||
context.report({ node, loc: {
|
||||
line: index + 1,
|
||||
column: match.index + 1
|
||||
}, message: "Unexpected tab character." });
|
||||
context.report({
|
||||
node,
|
||||
loc: {
|
||||
line: index + 1,
|
||||
column: match.index + 1
|
||||
},
|
||||
message: "Unexpected tab character."
|
||||
});
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -43,6 +43,10 @@ module.exports = {
|
||||
message: "It's not necessary to initialize '{{name}}' to undefined.",
|
||||
data: { name },
|
||||
fix(fixer) {
|
||||
if (node.parent.kind === "var") {
|
||||
return null;
|
||||
}
|
||||
|
||||
if (node.id.type === "ArrayPattern" || node.id.type === "ObjectPattern") {
|
||||
|
||||
// Don't fix destructuring assignment to `undefined`.
|
||||
|
||||
@@ -213,13 +213,13 @@ function getEncloseFunctionDeclaration(reference) {
|
||||
* @returns {void}
|
||||
*/
|
||||
function updateModifiedFlag(conditions, modifiers) {
|
||||
let funcNode, funcVar;
|
||||
|
||||
for (let i = 0; i < conditions.length; ++i) {
|
||||
const condition = conditions[i];
|
||||
|
||||
for (let j = 0; !condition.modified && j < modifiers.length; ++j) {
|
||||
const modifier = modifiers[j];
|
||||
let funcNode, funcVar;
|
||||
|
||||
/*
|
||||
* Besides checking for the condition being in the loop, we want to
|
||||
|
||||
@@ -64,8 +64,6 @@ module.exports = {
|
||||
create(context) {
|
||||
const sourceCode = context.getSourceCode();
|
||||
|
||||
const DEFINED_MESSAGE = "'{{name}}' is defined but never used.";
|
||||
const ASSIGNED_MESSAGE = "'{{name}}' is assigned a value but never used.";
|
||||
const REST_PROPERTY_TYPE = /^(?:Experimental)?RestProperty$/;
|
||||
|
||||
const config = {
|
||||
@@ -100,6 +98,49 @@ module.exports = {
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the warning message about the variable being
|
||||
* defined and unused, including the ignore pattern if configured.
|
||||
* @param {Variable} unusedVar - eslint-scope variable object.
|
||||
* @returns {string} The warning message to be used with this unused variable.
|
||||
*/
|
||||
function getDefinedMessage(unusedVar) {
|
||||
let type;
|
||||
let pattern;
|
||||
|
||||
if (config.varsIgnorePattern) {
|
||||
type = "vars";
|
||||
pattern = config.varsIgnorePattern.toString();
|
||||
}
|
||||
|
||||
if (unusedVar.defs && unusedVar.defs[0] && unusedVar.defs[0].type) {
|
||||
const defType = unusedVar.defs[0].type;
|
||||
|
||||
if (defType === "CatchClause" && config.caughtErrorsIgnorePattern) {
|
||||
type = "args";
|
||||
pattern = config.caughtErrorsIgnorePattern.toString();
|
||||
} else if (defType === "Parameter" && config.argsIgnorePattern) {
|
||||
type = "args";
|
||||
pattern = config.argsIgnorePattern.toString();
|
||||
}
|
||||
}
|
||||
|
||||
const additional = type ? ` Allowed unused ${type} must match ${pattern}.` : "";
|
||||
|
||||
return `'{{name}}' is defined but never used.${additional}`;
|
||||
}
|
||||
|
||||
/**
|
||||
* Generate the warning message about the variable being
|
||||
* assigned and unused, including the ignore pattern if configured.
|
||||
* @returns {string} The warning message to be used with this unused variable.
|
||||
*/
|
||||
function getAssignedMessage() {
|
||||
const additional = config.varsIgnorePattern ? ` Allowed unused vars must match ${config.varsIgnorePattern.toString()}.` : "";
|
||||
|
||||
return `'{{name}}' is assigned a value but never used.${additional}`;
|
||||
}
|
||||
|
||||
//--------------------------------------------------------------------------
|
||||
// Helpers
|
||||
//--------------------------------------------------------------------------
|
||||
@@ -586,13 +627,15 @@ module.exports = {
|
||||
context.report({
|
||||
node: programNode,
|
||||
loc: getLocation(unusedVar),
|
||||
message: DEFINED_MESSAGE,
|
||||
message: getDefinedMessage(unusedVar),
|
||||
data: unusedVar
|
||||
});
|
||||
} else if (unusedVar.defs.length > 0) {
|
||||
context.report({
|
||||
node: unusedVar.identifiers[0],
|
||||
message: unusedVar.references.some(ref => ref.isWrite()) ? ASSIGNED_MESSAGE : DEFINED_MESSAGE,
|
||||
message: unusedVar.references.some(ref => ref.isWrite())
|
||||
? getAssignedMessage()
|
||||
: getDefinedMessage(unusedVar),
|
||||
data: unusedVar
|
||||
});
|
||||
}
|
||||
|
||||
@@ -174,7 +174,7 @@ module.exports = {
|
||||
options.arraysInObjectsException && astUtils.isClosingBracketToken(penultimate) ||
|
||||
options.objectsInObjectsException && astUtils.isClosingBraceToken(penultimate)
|
||||
);
|
||||
const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.start).type;
|
||||
const penultimateType = shouldCheckPenultimate && sourceCode.getNodeByRangeIndex(penultimate.range[0]).type;
|
||||
|
||||
const closingCurlyBraceMustBeSpaced = (
|
||||
options.arraysInObjectsException && penultimateType === "ArrayExpression" ||
|
||||
|
||||
@@ -111,7 +111,7 @@ module.exports = {
|
||||
* @returns {boolean} Whether or not the token is followed by a blank line.
|
||||
*/
|
||||
function getFirstBlockToken(token) {
|
||||
let prev = token,
|
||||
let prev,
|
||||
first = token;
|
||||
|
||||
do {
|
||||
@@ -129,7 +129,7 @@ module.exports = {
|
||||
*/
|
||||
function getLastBlockToken(token) {
|
||||
let last = token,
|
||||
next = token;
|
||||
next;
|
||||
|
||||
do {
|
||||
next = last;
|
||||
@@ -202,7 +202,7 @@ module.exports = {
|
||||
node,
|
||||
loc: { line: tokenBeforeFirst.loc.start.line, column: tokenBeforeFirst.loc.start.column },
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange([tokenBeforeFirst.end, firstBlockToken.start - firstBlockToken.loc.start.column], "\n");
|
||||
return fixer.replaceTextRange([tokenBeforeFirst.range[1], firstBlockToken.range[0] - firstBlockToken.loc.start.column], "\n");
|
||||
},
|
||||
message: NEVER_MESSAGE
|
||||
});
|
||||
@@ -215,7 +215,7 @@ module.exports = {
|
||||
loc: { line: tokenAfterLast.loc.end.line, column: tokenAfterLast.loc.end.column - 1 },
|
||||
message: NEVER_MESSAGE,
|
||||
fix(fixer) {
|
||||
return fixer.replaceTextRange([lastBlockToken.end, tokenAfterLast.start - tokenAfterLast.loc.start.column], "\n");
|
||||
return fixer.replaceTextRange([lastBlockToken.range[1], tokenAfterLast.range[0] - tokenAfterLast.loc.start.column], "\n");
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
@@ -93,7 +93,7 @@ function isBlockLikeStatement(sourceCode, node) {
|
||||
|
||||
// Checks the last token is a closing brace of blocks.
|
||||
const lastToken = sourceCode.getLastToken(node, astUtils.isNotSemicolonToken);
|
||||
const belongingNode = astUtils.isClosingBraceToken(lastToken)
|
||||
const belongingNode = lastToken && astUtils.isClosingBraceToken(lastToken)
|
||||
? sourceCode.getNodeByRangeIndex(lastToken.range[0])
|
||||
: null;
|
||||
|
||||
@@ -205,14 +205,14 @@ function verifyForAny() {
|
||||
* blank lines automatically.
|
||||
*
|
||||
* @param {RuleContext} context The rule context to report.
|
||||
* @param {ASTNode} prevNode The previous node to check.
|
||||
* @param {ASTNode} _ Unused. The previous node to check.
|
||||
* @param {ASTNode} nextNode The next node to check.
|
||||
* @param {Array<Token[]>} paddingLines The array of token pairs that blank
|
||||
* lines exist between the pair.
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function verifyForNever(context, prevNode, nextNode, paddingLines) {
|
||||
function verifyForNever(context, _, nextNode, paddingLines) {
|
||||
if (paddingLines.length === 0) {
|
||||
return;
|
||||
}
|
||||
|
||||
@@ -88,7 +88,6 @@ function getCallbackInfo(node) {
|
||||
parent.parent.arguments.length === 1 &&
|
||||
parent.parent.arguments[0].type === "ThisExpression"
|
||||
);
|
||||
node = parent;
|
||||
parent = parent.parent;
|
||||
} else {
|
||||
return retv;
|
||||
@@ -133,7 +132,7 @@ function hasDuplicateParams(paramsList) {
|
||||
module.exports = {
|
||||
meta: {
|
||||
docs: {
|
||||
description: "require arrow functions as callbacks",
|
||||
description: "require using arrow functions for callbacks",
|
||||
category: "ECMAScript 6",
|
||||
recommended: false
|
||||
},
|
||||
|
||||
@@ -83,10 +83,14 @@ module.exports = {
|
||||
* @returns {void}
|
||||
*/
|
||||
function report(node, existing, substitute) {
|
||||
context.report({ node, message: "Avoid using {{existing}}, instead use {{substitute}}.", data: {
|
||||
existing,
|
||||
substitute
|
||||
} });
|
||||
context.report({
|
||||
node,
|
||||
message: "Avoid using {{existing}}, instead use {{substitute}}.",
|
||||
data: {
|
||||
existing,
|
||||
substitute
|
||||
}
|
||||
});
|
||||
}
|
||||
|
||||
return {
|
||||
|
||||
@@ -74,7 +74,7 @@ function startsWithTemplateCurly(node) {
|
||||
return startsWithTemplateCurly(node.left);
|
||||
}
|
||||
if (node.type === "TemplateLiteral") {
|
||||
return node.expressions.length && node.quasis.length && node.quasis[0].start === node.quasis[0].end;
|
||||
return node.expressions.length && node.quasis.length && node.quasis[0].range[0] === node.quasis[0].range[1];
|
||||
}
|
||||
return node.type !== "Literal" || typeof node.value !== "string";
|
||||
}
|
||||
@@ -89,7 +89,7 @@ function endsWithTemplateCurly(node) {
|
||||
return startsWithTemplateCurly(node.right);
|
||||
}
|
||||
if (node.type === "TemplateLiteral") {
|
||||
return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].start === node.quasis[node.quasis.length - 1].end;
|
||||
return node.expressions.length && node.quasis.length && node.quasis[node.quasis.length - 1].range[0] === node.quasis[node.quasis.length - 1].range[1];
|
||||
}
|
||||
return node.type !== "Literal" || typeof node.value !== "string";
|
||||
}
|
||||
|
||||
@@ -135,13 +135,14 @@ module.exports = {
|
||||
*/
|
||||
function checkUnnecessaryQuotes(node) {
|
||||
const key = node.key;
|
||||
let tokens;
|
||||
|
||||
if (node.method || node.computed || node.shorthand) {
|
||||
return;
|
||||
}
|
||||
|
||||
if (key.type === "Literal" && typeof key.value === "string") {
|
||||
let tokens;
|
||||
|
||||
try {
|
||||
tokens = espree.tokenize(key.value);
|
||||
} catch (e) {
|
||||
@@ -215,7 +216,6 @@ module.exports = {
|
||||
|
||||
node.properties.forEach(property => {
|
||||
const key = property.key;
|
||||
let tokens;
|
||||
|
||||
if (!key || property.method || property.computed || property.shorthand) {
|
||||
return;
|
||||
@@ -226,6 +226,8 @@ module.exports = {
|
||||
quotedProps.push(property);
|
||||
|
||||
if (checkQuotesRedundancy) {
|
||||
let tokens;
|
||||
|
||||
try {
|
||||
tokens = espree.tokenize(key.value);
|
||||
} catch (e) {
|
||||
|
||||
@@ -229,10 +229,9 @@ module.exports = {
|
||||
Literal(node) {
|
||||
const val = node.value,
|
||||
rawVal = node.raw;
|
||||
let isValid;
|
||||
|
||||
if (settings && typeof val === "string") {
|
||||
isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
|
||||
let isValid = (quoteOption === "backtick" && isAllowedAsNonBacktick(node)) ||
|
||||
isJSXLiteral(node) ||
|
||||
astUtils.isSurroundedBy(rawVal, settings.quote);
|
||||
|
||||
|
||||
@@ -82,11 +82,11 @@ module.exports = {
|
||||
*/
|
||||
function checkPrecedingSpace(node) {
|
||||
const precedingToken = sourceCode.getTokenBefore(node);
|
||||
let requireSpace;
|
||||
|
||||
if (precedingToken && !isConflicted(precedingToken) && astUtils.isTokenOnSameLine(precedingToken, node)) {
|
||||
const hasSpace = sourceCode.isSpaceBetweenTokens(precedingToken, node);
|
||||
const parent = context.getAncestors().pop();
|
||||
let requireSpace;
|
||||
|
||||
if (parent.type === "FunctionExpression" || parent.type === "FunctionDeclaration") {
|
||||
requireSpace = checkFunctions;
|
||||
|
||||
@@ -112,7 +112,7 @@ module.exports = {
|
||||
const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode);
|
||||
|
||||
if (nonSpacedNode) {
|
||||
if (!(int32Hint && sourceCode.getText(node).substr(-2) === "|0")) {
|
||||
if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
|
||||
report(node, nonSpacedNode);
|
||||
}
|
||||
}
|
||||
|
||||
@@ -76,21 +76,19 @@ module.exports = {
|
||||
|
||||
/**
|
||||
* Checks if an override exists for a given operator.
|
||||
* @param {ASTnode} node AST node
|
||||
* @param {string} operator Operator
|
||||
* @returns {boolean} Whether or not an override has been provided for the operator
|
||||
*/
|
||||
function overrideExistsForOperator(node, operator) {
|
||||
function overrideExistsForOperator(operator) {
|
||||
return options.overrides && options.overrides.hasOwnProperty(operator);
|
||||
}
|
||||
|
||||
/**
|
||||
* Gets the value that the override was set to for this operator
|
||||
* @param {ASTnode} node AST node
|
||||
* @param {string} operator Operator
|
||||
* @returns {boolean} Whether or not an override enforces a space with this operator
|
||||
*/
|
||||
function overrideEnforcesSpaces(node, operator) {
|
||||
function overrideEnforcesSpaces(operator) {
|
||||
return options.overrides[operator];
|
||||
}
|
||||
|
||||
@@ -153,8 +151,8 @@ module.exports = {
|
||||
function checkUnaryWordOperatorForSpaces(node, firstToken, secondToken, word) {
|
||||
word = word || firstToken.value;
|
||||
|
||||
if (overrideExistsForOperator(node, word)) {
|
||||
if (overrideEnforcesSpaces(node, word)) {
|
||||
if (overrideExistsForOperator(word)) {
|
||||
if (overrideEnforcesSpaces(word)) {
|
||||
verifyWordHasSpaces(node, firstToken, secondToken, word);
|
||||
} else {
|
||||
verifyWordDoesntHaveSpaces(node, firstToken, secondToken, word);
|
||||
@@ -292,8 +290,8 @@ module.exports = {
|
||||
|
||||
const operator = node.prefix ? tokens[0].value : tokens[1].value;
|
||||
|
||||
if (overrideExistsForOperator(node, operator)) {
|
||||
if (overrideEnforcesSpaces(node, operator)) {
|
||||
if (overrideExistsForOperator(operator)) {
|
||||
if (overrideEnforcesSpaces(operator)) {
|
||||
verifyNonWordsHaveSpaces(node, firstToken, secondToken);
|
||||
} else {
|
||||
verifyNonWordsDontHaveSpaces(node, firstToken, secondToken);
|
||||
|
||||
@@ -303,9 +303,9 @@ module.exports = {
|
||||
node,
|
||||
fix(fixer) {
|
||||
if (requireSpace) {
|
||||
return fixer.insertTextAfterRange([node.start, node.end - 2], " ");
|
||||
return fixer.insertTextAfterRange([node.range[0], node.range[1] - 2], " ");
|
||||
}
|
||||
const end = node.end - 2,
|
||||
const end = node.range[1] - 2,
|
||||
start = end - match[0].length;
|
||||
|
||||
return fixer.replaceTextRange([start, end], "");
|
||||
|
||||
@@ -231,11 +231,11 @@ module.exports = {
|
||||
hasConstructor = false,
|
||||
isInterface = false,
|
||||
isOverride = false,
|
||||
isAbstract = false,
|
||||
jsdoc;
|
||||
isAbstract = false;
|
||||
|
||||
// make sure only to validate JSDoc comments
|
||||
if (jsdocNode) {
|
||||
let jsdoc;
|
||||
|
||||
try {
|
||||
jsdoc = doctrine.parse(jsdocNode.value, {
|
||||
@@ -362,14 +362,22 @@ module.exports = {
|
||||
// TODO(nzakas): Figure out logical things to do with destructured, default, rest params
|
||||
if (param.type === "Identifier") {
|
||||
if (jsdocParams[i] && (name !== jsdocParams[i])) {
|
||||
context.report({ node: jsdocNode, message: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.", data: {
|
||||
name,
|
||||
jsdocName: jsdocParams[i]
|
||||
} });
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
message: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",
|
||||
data: {
|
||||
name,
|
||||
jsdocName: jsdocParams[i]
|
||||
}
|
||||
});
|
||||
} else if (!params[name] && !isOverride) {
|
||||
context.report({ node: jsdocNode, message: "Missing JSDoc for parameter '{{name}}'.", data: {
|
||||
name
|
||||
} });
|
||||
context.report({
|
||||
node: jsdocNode,
|
||||
message: "Missing JSDoc for parameter '{{name}}'.",
|
||||
data: {
|
||||
name
|
||||
}
|
||||
});
|
||||
}
|
||||
}
|
||||
});
|
||||
|
||||
@@ -1,62 +0,0 @@
|
||||
/**
|
||||
* @fileoverview Helpers to test EventGenerator interface.
|
||||
* @author Toru Nagashima
|
||||
*/
|
||||
"use strict";
|
||||
|
||||
/* global describe, it */
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Requirements
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
const assert = require("assert");
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Public Interface
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
module.exports = {
|
||||
|
||||
/**
|
||||
* Overrideable `describe` function to test.
|
||||
* @param {string} text - A description.
|
||||
* @param {Function} method - A test logic.
|
||||
* @returns {any} The returned value with the test logic.
|
||||
*/
|
||||
describe: (typeof describe === "function") ? describe : /* istanbul ignore next */ function(text, method) {
|
||||
return method.apply(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Overrideable `it` function to test.
|
||||
* @param {string} text - A description.
|
||||
* @param {Function} method - A test logic.
|
||||
* @returns {any} The returned value with the test logic.
|
||||
*/
|
||||
it: (typeof it === "function") ? it : /* istanbul ignore next */ function(text, method) {
|
||||
return method.apply(this);
|
||||
},
|
||||
|
||||
/**
|
||||
* Does some tests to check a given object implements the EventGenerator interface.
|
||||
* @param {Object} instance - An object to check.
|
||||
* @returns {void}
|
||||
*/
|
||||
testEventGeneratorInterface(instance) {
|
||||
this.describe("should implement EventGenerator interface", () => {
|
||||
this.it("should have `emitter` property.", () => {
|
||||
assert.equal(typeof instance.emitter, "object");
|
||||
assert.equal(typeof instance.emitter.emit, "function");
|
||||
});
|
||||
|
||||
this.it("should have `enterNode` property.", () => {
|
||||
assert.equal(typeof instance.enterNode, "function");
|
||||
});
|
||||
|
||||
this.it("should have `leaveNode` property.", () => {
|
||||
assert.equal(typeof instance.leaveNode, "function");
|
||||
});
|
||||
});
|
||||
}
|
||||
};
|
||||
@@ -9,7 +9,7 @@
|
||||
/*
|
||||
* This is a wrapper around mocha to allow for DRY unittests for eslint
|
||||
* Format:
|
||||
* RuleTester.add("{ruleName}", {
|
||||
* RuleTester.run("{ruleName}", {
|
||||
* valid: [
|
||||
* "{code}",
|
||||
* { code: "{code}", options: {options}, globals: {globals}, parser: "{parser}", settings: {settings} }
|
||||
@@ -159,7 +159,8 @@ class RuleTester {
|
||||
|
||||
// we have to clone because merge uses the first argument for recipient
|
||||
lodash.cloneDeep(defaultConfig),
|
||||
testerConfig
|
||||
testerConfig,
|
||||
{ rules: { "rule-tester/validate-ast": "error" } }
|
||||
);
|
||||
|
||||
/**
|
||||
@@ -250,7 +251,6 @@ class RuleTester {
|
||||
const testerConfig = this.testerConfig,
|
||||
requiredScenarios = ["valid", "invalid"],
|
||||
scenarioErrors = [],
|
||||
result = {},
|
||||
linter = this.linter;
|
||||
|
||||
if (lodash.isNil(test) || typeof test !== "object") {
|
||||
@@ -269,16 +269,13 @@ class RuleTester {
|
||||
].concat(scenarioErrors).join("\n"));
|
||||
}
|
||||
|
||||
/* eslint-disable no-shadow */
|
||||
|
||||
/**
|
||||
* Run the rule for the given item
|
||||
* @param {string} ruleName name of the rule
|
||||
* @param {string|Object} item Item to run the rule against
|
||||
* @returns {Object} Eslint run result
|
||||
* @private
|
||||
*/
|
||||
function runRuleForItem(ruleName, item) {
|
||||
function runRuleForItem(item) {
|
||||
let config = lodash.cloneDeep(testerConfig),
|
||||
code, filename, beforeAST, afterAST;
|
||||
|
||||
@@ -310,7 +307,17 @@ class RuleTester {
|
||||
config.rules[ruleName] = 1;
|
||||
}
|
||||
|
||||
linter.defineRule(ruleName, rule);
|
||||
linter.defineRule(ruleName, Object.assign({}, rule, {
|
||||
|
||||
// Create a wrapper rule that freezes the `context` properties.
|
||||
create(context) {
|
||||
freezeDeeply(context.options);
|
||||
freezeDeeply(context.settings);
|
||||
freezeDeeply(context.parserOptions);
|
||||
|
||||
return (typeof rule === "function" ? rule : rule.create)(context);
|
||||
}
|
||||
}));
|
||||
|
||||
const schema = validator.getRuleOptionsSchema(ruleName, linter.rules);
|
||||
|
||||
@@ -335,55 +342,20 @@ class RuleTester {
|
||||
* The goal is to check whether or not AST was modified when
|
||||
* running the rule under test.
|
||||
*/
|
||||
linter.reset();
|
||||
linter.defineRule("rule-tester/validate-ast", () => ({
|
||||
Program(node) {
|
||||
beforeAST = cloneDeeplyExcludesParent(node);
|
||||
},
|
||||
"Program:exit"(node) {
|
||||
afterAST = node;
|
||||
}
|
||||
}));
|
||||
|
||||
linter.on("Program", node => {
|
||||
beforeAST = cloneDeeplyExcludesParent(node);
|
||||
});
|
||||
|
||||
linter.on("Program:exit", node => {
|
||||
afterAST = node;
|
||||
});
|
||||
|
||||
// Freezes rule-context properties.
|
||||
const originalGet = linter.rules.get;
|
||||
|
||||
try {
|
||||
linter.rules.get = function(ruleId) {
|
||||
const rule = originalGet.call(linter.rules, ruleId);
|
||||
|
||||
if (typeof rule === "function") {
|
||||
return function(context) {
|
||||
Object.freeze(context);
|
||||
freezeDeeply(context.options);
|
||||
freezeDeeply(context.settings);
|
||||
freezeDeeply(context.parserOptions);
|
||||
|
||||
return rule(context);
|
||||
};
|
||||
}
|
||||
return {
|
||||
meta: rule.meta,
|
||||
create(context) {
|
||||
Object.freeze(context);
|
||||
freezeDeeply(context.options);
|
||||
freezeDeeply(context.settings);
|
||||
freezeDeeply(context.parserOptions);
|
||||
|
||||
return rule.create(context);
|
||||
}
|
||||
};
|
||||
|
||||
};
|
||||
|
||||
return {
|
||||
messages: linter.verify(code, config, filename, true),
|
||||
beforeAST,
|
||||
afterAST: cloneDeeplyExcludesParent(afterAST)
|
||||
};
|
||||
} finally {
|
||||
linter.rules.get = originalGet;
|
||||
}
|
||||
return {
|
||||
messages: linter.verify(code, config, filename, true),
|
||||
beforeAST,
|
||||
afterAST: cloneDeeplyExcludesParent(afterAST)
|
||||
};
|
||||
}
|
||||
|
||||
/**
|
||||
@@ -404,13 +376,12 @@ class RuleTester {
|
||||
/**
|
||||
* Check if the template is valid or not
|
||||
* all valid cases go through this
|
||||
* @param {string} ruleName name of the rule
|
||||
* @param {string|Object} item Item to run the rule against
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function testValidTemplate(ruleName, item) {
|
||||
const result = runRuleForItem(ruleName, item);
|
||||
function testValidTemplate(item) {
|
||||
const result = runRuleForItem(item);
|
||||
const messages = result.messages;
|
||||
|
||||
assert.equal(messages.length, 0, util.format("Should have no errors but had %d: %s",
|
||||
@@ -444,16 +415,15 @@ class RuleTester {
|
||||
/**
|
||||
* Check if the template is invalid or not
|
||||
* all invalid cases go through this.
|
||||
* @param {string} ruleName name of the rule
|
||||
* @param {string|Object} item Item to run the rule against
|
||||
* @returns {void}
|
||||
* @private
|
||||
*/
|
||||
function testInvalidTemplate(ruleName, item) {
|
||||
function testInvalidTemplate(item) {
|
||||
assert.ok(item.errors || item.errors === 0,
|
||||
`Did not specify errors for an invalid test of ${ruleName}`);
|
||||
|
||||
const result = runRuleForItem(ruleName, item);
|
||||
const result = runRuleForItem(item);
|
||||
const messages = result.messages;
|
||||
|
||||
|
||||
@@ -525,7 +495,7 @@ class RuleTester {
|
||||
"Expected no autofixes to be suggested"
|
||||
);
|
||||
} else {
|
||||
const fixResult = SourceCodeFixer.applyFixes(linter.getSourceCode(), messages);
|
||||
const fixResult = SourceCodeFixer.applyFixes(item.code, messages);
|
||||
|
||||
assert.equal(fixResult.output, item.output, "Output is incorrect.");
|
||||
}
|
||||
@@ -543,7 +513,7 @@ class RuleTester {
|
||||
test.valid.forEach(valid => {
|
||||
RuleTester.it(typeof valid === "object" ? valid.code : valid, () => {
|
||||
linter.defineRules(this.rules);
|
||||
testValidTemplate(ruleName, valid);
|
||||
testValidTemplate(valid);
|
||||
});
|
||||
});
|
||||
});
|
||||
@@ -552,13 +522,11 @@ class RuleTester {
|
||||
test.invalid.forEach(invalid => {
|
||||
RuleTester.it(invalid.code, () => {
|
||||
linter.defineRules(this.rules);
|
||||
testInvalidTemplate(ruleName, invalid);
|
||||
testInvalidTemplate(invalid);
|
||||
});
|
||||
});
|
||||
});
|
||||
});
|
||||
|
||||
return result.suite;
|
||||
}
|
||||
}
|
||||
|
||||
|
||||
@@ -84,11 +84,11 @@ function display(data) {
|
||||
}
|
||||
});
|
||||
|
||||
const table = rows.map(row =>
|
||||
const table = rows.map(row => (
|
||||
row
|
||||
.map((cell, index) => ALIGN[index](cell, widths[index]))
|
||||
.join(" | ")
|
||||
);
|
||||
));
|
||||
|
||||
table.splice(1, 0, widths.map((w, index) => {
|
||||
if (index !== 0 && index !== widths.length - 1) {
|
||||
|
||||
@@ -0,0 +1,160 @@
|
||||
/**
|
||||
* @fileoverview A module that filters reported problems based on `eslint-disable` and `eslint-enable` comments
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
const lodash = require("lodash");
|
||||
|
||||
/**
|
||||
* Compares the locations of two objects in a source file
|
||||
* @param {{line: number, column: number}} itemA The first object
|
||||
* @param {{line: number, column: number}} itemB The second object
|
||||
* @returns {number} A value less than 1 if itemA appears before itemB in the source file, greater than 1 if
|
||||
* itemA appears after itemB in the source file, or 0 if itemA and itemB have the same location.
|
||||
*/
|
||||
function compareLocations(itemA, itemB) {
|
||||
return itemA.line - itemB.line || itemA.column - itemB.column;
|
||||
}
|
||||
|
||||
/**
|
||||
* This is the same as the exported function, except that it
|
||||
* doesn't handle disable-line and disable-next-line directives, and it always reports unused
|
||||
* disable directives.
|
||||
* @param {Object} options options for applying directives. This is the same as the options
|
||||
* for the exported function, except that `reportUnusedDisableDirectives` is not supported
|
||||
* (this function always reports unused disable directives).
|
||||
* @returns {{problems: Problem[], unusedDisableDirectives: Problem[]}} An object with a list
|
||||
* of filtered problems and unused eslint-disable directives
|
||||
*/
|
||||
function applyDirectives(options) {
|
||||
const problems = [];
|
||||
let nextDirectiveIndex = 0;
|
||||
let currentGlobalDisableDirective = null;
|
||||
const disabledRuleMap = new Map();
|
||||
|
||||
// enabledRules is only used when there is a current global disable directive.
|
||||
const enabledRules = new Set();
|
||||
const usedDisableDirectives = new Set();
|
||||
|
||||
for (const problem of options.problems) {
|
||||
while (
|
||||
nextDirectiveIndex < options.directives.length &&
|
||||
compareLocations(options.directives[nextDirectiveIndex], problem) <= 0
|
||||
) {
|
||||
const directive = options.directives[nextDirectiveIndex++];
|
||||
|
||||
switch (directive.type) {
|
||||
case "disable":
|
||||
if (directive.ruleId === null) {
|
||||
currentGlobalDisableDirective = directive;
|
||||
disabledRuleMap.clear();
|
||||
enabledRules.clear();
|
||||
} else if (currentGlobalDisableDirective) {
|
||||
enabledRules.delete(directive.ruleId);
|
||||
disabledRuleMap.set(directive.ruleId, directive);
|
||||
} else {
|
||||
disabledRuleMap.set(directive.ruleId, directive);
|
||||
}
|
||||
break;
|
||||
|
||||
case "enable":
|
||||
if (directive.ruleId === null) {
|
||||
currentGlobalDisableDirective = null;
|
||||
disabledRuleMap.clear();
|
||||
} else if (currentGlobalDisableDirective) {
|
||||
enabledRules.add(directive.ruleId);
|
||||
disabledRuleMap.delete(directive.ruleId);
|
||||
} else {
|
||||
disabledRuleMap.delete(directive.ruleId);
|
||||
}
|
||||
break;
|
||||
|
||||
// no default
|
||||
}
|
||||
}
|
||||
|
||||
if (disabledRuleMap.has(problem.ruleId)) {
|
||||
usedDisableDirectives.add(disabledRuleMap.get(problem.ruleId));
|
||||
} else if (currentGlobalDisableDirective && !enabledRules.has(problem.ruleId)) {
|
||||
usedDisableDirectives.add(currentGlobalDisableDirective);
|
||||
} else {
|
||||
problems.push(problem);
|
||||
}
|
||||
}
|
||||
|
||||
const unusedDisableDirectives = options.directives
|
||||
.filter(directive => directive.type === "disable" && !usedDisableDirectives.has(directive))
|
||||
.map(directive => ({
|
||||
ruleId: null,
|
||||
message: directive.ruleId
|
||||
? `Unused eslint-disable directive (no problems were reported from '${directive.ruleId}').`
|
||||
: "Unused eslint-disable directive (no problems were reported).",
|
||||
line: directive.unprocessedDirective.line,
|
||||
column: directive.unprocessedDirective.column,
|
||||
severity: 2,
|
||||
source: null,
|
||||
nodeType: null
|
||||
}));
|
||||
|
||||
return { problems, unusedDisableDirectives };
|
||||
}
|
||||
|
||||
/**
|
||||
* Given a list of directive comments (i.e. metadata about eslint-disable and eslint-enable comments) and a list
|
||||
* of reported problems, determines which problems should be reported.
|
||||
* @param {Object} options Information about directives and problems
|
||||
* @param {{
|
||||
* type: ("disable"|"enable"|"disable-line"|"disable-next-line"),
|
||||
* ruleId: (string|null),
|
||||
* line: number,
|
||||
* column: number
|
||||
* }} options.directives Directive comments found in the file, with one-based columns.
|
||||
* Two directive comments can only have the same location if they also have the same type (e.g. a single eslint-disable
|
||||
* comment for two different rules is represented as two directives).
|
||||
* @param {{ruleId: (string|null), line: number, column: number}[]} options.problems
|
||||
* A list of problems reported by rules, sorted by increasing location in the file, with one-based columns.
|
||||
* @param {boolean} options.reportUnusedDisableDirectives If `true`, adds additional problems for unused directives
|
||||
* @returns {{ruleId: (string|null), line: number, column: number}[]}
|
||||
* A list of reported problems that were not disabled by the directive comments.
|
||||
*/
|
||||
module.exports = options => {
|
||||
const blockDirectives = options.directives
|
||||
.filter(directive => directive.type === "disable" || directive.type === "enable")
|
||||
.map(directive => Object.assign({}, directive, { unprocessedDirective: directive }))
|
||||
.sort(compareLocations);
|
||||
|
||||
const lineDirectives = lodash.flatMap(options.directives, directive => {
|
||||
switch (directive.type) {
|
||||
case "disable":
|
||||
case "enable":
|
||||
return [];
|
||||
|
||||
case "disable-line":
|
||||
return [
|
||||
{ type: "disable", line: directive.line, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
|
||||
{ type: "enable", line: directive.line + 1, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
|
||||
];
|
||||
|
||||
case "disable-next-line":
|
||||
return [
|
||||
{ type: "disable", line: directive.line + 1, column: 1, ruleId: directive.ruleId, unprocessedDirective: directive },
|
||||
{ type: "enable", line: directive.line + 2, column: 0, ruleId: directive.ruleId, unprocessedDirective: directive }
|
||||
];
|
||||
|
||||
default:
|
||||
throw new TypeError(`Unrecognized directive type '${directive.type}'`);
|
||||
}
|
||||
}).sort(compareLocations);
|
||||
|
||||
const blockDirectivesResult = applyDirectives({ problems: options.problems, directives: blockDirectives });
|
||||
const lineDirectivesResult = applyDirectives({ problems: blockDirectivesResult.problems, directives: lineDirectives });
|
||||
|
||||
return options.reportUnusedDisableDirectives
|
||||
? lineDirectivesResult.problems
|
||||
.concat(blockDirectivesResult.unusedDisableDirectives)
|
||||
.concat(lineDirectivesResult.unusedDisableDirectives)
|
||||
.sort(compareLocations)
|
||||
: lineDirectivesResult.problems;
|
||||
};
|
||||
@@ -57,8 +57,7 @@ class FixTracker {
|
||||
retainEnclosingFunction(node) {
|
||||
const functionNode = astUtils.getUpperFunction(node);
|
||||
|
||||
return this.retainRange(
|
||||
functionNode ? functionNode.range : this.sourceCode.ast.range);
|
||||
return this.retainRange(functionNode ? functionNode.range : this.sourceCode.ast.range);
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -194,7 +194,7 @@ const parseSelector = lodash.memoize(rawSelector => {
|
||||
*
|
||||
* ```ts
|
||||
* interface EventGenerator {
|
||||
* emitter: EventEmitter;
|
||||
* emitter: SafeEmitter;
|
||||
* enterNode(node: ASTNode): void;
|
||||
* leaveNode(node: ASTNode): void;
|
||||
* }
|
||||
@@ -203,8 +203,10 @@ const parseSelector = lodash.memoize(rawSelector => {
|
||||
class NodeEventGenerator {
|
||||
|
||||
/**
|
||||
* @param {EventEmitter} emitter - An event emitter which is the destination of events. This emitter must already
|
||||
* @param {SafeEmitter} emitter
|
||||
* An SafeEmitter which is the destination of events. This emitter must already
|
||||
* have registered listeners for all of the events that it needs to listen for.
|
||||
* (See lib/util/safe-emitter.js for more details on `SafeEmitter`.)
|
||||
* @returns {NodeEventGenerator} new instance
|
||||
*/
|
||||
constructor(emitter) {
|
||||
@@ -215,23 +217,7 @@ class NodeEventGenerator {
|
||||
this.anyTypeEnterSelectors = [];
|
||||
this.anyTypeExitSelectors = [];
|
||||
|
||||
const eventNames = typeof emitter.eventNames === "function"
|
||||
|
||||
// Use the built-in eventNames() function if available (Node 6+)
|
||||
? emitter.eventNames()
|
||||
|
||||
/*
|
||||
* Otherwise, use the private _events property.
|
||||
* Using a private property isn't ideal here, but this seems to
|
||||
* be the best way to get a list of event names without overriding
|
||||
* addEventListener, which would hurt performance. This property
|
||||
* is widely used and unlikely to be removed in a future version
|
||||
* (see https://github.com/nodejs/node/issues/1817). Also, future
|
||||
* node versions will have eventNames() anyway.
|
||||
*/
|
||||
: Object.keys(emitter._events); // eslint-disable-line no-underscore-dangle
|
||||
|
||||
eventNames.forEach(rawSelector => {
|
||||
emitter.eventNames().forEach(rawSelector => {
|
||||
const selector = parseSelector(rawSelector);
|
||||
|
||||
if (selector.listenerTypes) {
|
||||
|
||||
@@ -53,22 +53,39 @@ function installSyncSaveDev(packages) {
|
||||
if (!Array.isArray(packages)) {
|
||||
packages = [packages];
|
||||
}
|
||||
spawn.sync("npm", ["i", "--save-dev"].concat(packages), { stdio: "inherit" });
|
||||
const npmProcess = spawn.sync("npm", ["i", "--save-dev"].concat(packages),
|
||||
{ stdio: "inherit" });
|
||||
const error = npmProcess.error;
|
||||
|
||||
if (error && error.code === "ENOENT") {
|
||||
const pluralS = packages.length > 1 ? "s" : "";
|
||||
|
||||
log.error(`Could not execute npm. Please install the following package${pluralS} with your package manager of choice: ${packages.join(", ")}`);
|
||||
}
|
||||
}
|
||||
|
||||
/**
|
||||
* Fetch `peerDependencies` of the given package by `npm show` command.
|
||||
* @param {string} packageName The package name to fetch peerDependencies.
|
||||
* @returns {Object} Gotten peerDependencies.
|
||||
* @returns {Object} Gotten peerDependencies. Returns null if npm was not found.
|
||||
*/
|
||||
function fetchPeerDependencies(packageName) {
|
||||
const fetchedText = spawn.sync(
|
||||
const npmProcess = spawn.sync(
|
||||
"npm",
|
||||
["show", "--json", packageName, "peerDependencies"],
|
||||
{ encoding: "utf8" }
|
||||
).stdout.trim();
|
||||
);
|
||||
|
||||
const error = npmProcess.error;
|
||||
|
||||
if (error && error.code === "ENOENT") {
|
||||
return null;
|
||||
}
|
||||
const fetchedText = npmProcess.stdout.trim();
|
||||
|
||||
return JSON.parse(fetchedText || "{}");
|
||||
|
||||
|
||||
}
|
||||
|
||||
/**
|
||||
|
||||
@@ -0,0 +1,54 @@
|
||||
/**
|
||||
* @fileoverview A variant of EventEmitter which does not give listeners information about each other
|
||||
* @author Teddy Katz
|
||||
*/
|
||||
|
||||
"use strict";
|
||||
|
||||
//------------------------------------------------------------------------------
|
||||
// Typedefs
|
||||
//------------------------------------------------------------------------------
|
||||
|
||||
/**
|
||||
* An object describing an AST selector
|
||||
* @typedef {Object} SafeEmitter
|
||||
* @property {function(eventName: string, listenerFunc: Function): void} on Adds a listener for a given event name
|
||||
* @property {function(eventName: string, arg1?: any, arg2?: any, arg3?: any)} emit Emits an event with a given name.
|
||||
* This calls all the listeners that were listening for that name, with `arg1`, `arg2`, and `arg3` as arguments.
|
||||
* @property {function(): string[]} eventNames Gets the list of event names that have registered listeners.
|
||||
*/
|
||||
|
||||
/**
|
||||
* Creates an object which can listen for and emit events.
|
||||
* This is similar to the EventEmitter API in Node's standard library, but it has a few differences.
|
||||
* The goal is to allow multiple modules to attach arbitrary listeners to the same emitter, without
|
||||
* letting the modules know about each other at all.
|
||||
* 1. It has no special keys like `error` and `newListener`, which would allow modules to detect when
|
||||
* another module throws an error or registers a listener.
|
||||
* 2. It calls listener functions without any `this` value. (`EventEmitter` calls listeners with a
|
||||
* `this` value of the emitter instance, which would give listeners access to other listeners.)
|
||||
* 3. Events can be emitted with at most 3 arguments. (For example: when using `emitter.emit('foo', a, b, c)`,
|
||||
* the arguments `a`, `b`, and `c` will be passed to the listener functions.)
|
||||
* @returns {SafeEmitter} An emitter
|
||||
*/
|
||||
module.exports = () => {
|
||||
const listeners = Object.create(null);
|
||||
|
||||
return Object.freeze({
|
||||
on(eventName, listener) {
|
||||
if (eventName in listeners) {
|
||||
listeners[eventName].push(listener);
|
||||
} else {
|
||||
listeners[eventName] = [listener];
|
||||
}
|
||||
},
|
||||
emit(eventName, a, b, c) {
|
||||
if (eventName in listeners) {
|
||||
listeners[eventName].forEach(listener => listener(a, b, c));
|
||||
}
|
||||
},
|
||||
eventNames() {
|
||||
return Object.keys(listeners);
|
||||
}
|
||||
});
|
||||
};
|
||||
@@ -53,37 +53,28 @@ function SourceCodeFixer() {
|
||||
/**
|
||||
* Applies the fixes specified by the messages to the given text. Tries to be
|
||||
* smart about the fixes and won't apply fixes over the same area in the text.
|
||||
* @param {SourceCode} sourceCode The source code to apply the changes to.
|
||||
* @param {string} sourceText The text to apply the changes to.
|
||||
* @param {Message[]} messages The array of messages reported by ESLint.
|
||||
* @param {boolean|Function} [shouldFix=true] Determines whether each message should be fixed
|
||||
* @returns {Object} An object containing the fixed text and any unfixed messages.
|
||||
*/
|
||||
SourceCodeFixer.applyFixes = function(sourceCode, messages, shouldFix) {
|
||||
SourceCodeFixer.applyFixes = function(sourceText, messages, shouldFix) {
|
||||
debug("Applying fixes");
|
||||
|
||||
if (!sourceCode) {
|
||||
debug("No source code to fix");
|
||||
return {
|
||||
fixed: false,
|
||||
messages,
|
||||
output: ""
|
||||
};
|
||||
}
|
||||
|
||||
if (shouldFix === false) {
|
||||
debug("shouldFix parameter was false, not attempting fixes");
|
||||
return {
|
||||
fixed: false,
|
||||
messages,
|
||||
output: sourceCode.text
|
||||
output: sourceText
|
||||
};
|
||||
}
|
||||
|
||||
// clone the array
|
||||
const remainingMessages = [],
|
||||
fixes = [],
|
||||
bom = (sourceCode.hasBOM ? BOM : ""),
|
||||
text = sourceCode.text;
|
||||
bom = sourceText.startsWith(BOM) ? BOM : "",
|
||||
text = bom ? sourceText.slice(1) : sourceText;
|
||||
let lastPos = Number.NEGATIVE_INFINITY,
|
||||
output = bom;
|
||||
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"_resolved": "https://registry.npmjs.org/acorn/-/acorn-3.3.0.tgz",
|
||||
"_shasum": "45e37fb39e8da3f25baee3ff5369e2bb5f22017a",
|
||||
"_spec": "acorn@^3.0.4",
|
||||
"_where": "/Users/trott/io.js/tools/eslint-tmp/node_modules/eslint/node_modules/acorn-jsx",
|
||||
"_where": "/Users/apapirovski/Web/nodejs/tools/eslint-tmp/node_modules/eslint/node_modules/acorn-jsx",
|
||||
"bin": {
|
||||
"acorn": "./bin/acorn"
|
||||
},
|
||||
|
||||
+1
-1
@@ -21,7 +21,7 @@
|
||||
"_resolved": "https://registry.npmjs.org/acorn-jsx/-/acorn-jsx-3.0.1.tgz",
|
||||
"_shasum": "afdf9488fb1ecefc8348f6fb22f464e32a58b36b",
|
||||
"_spec": "acorn-jsx@^3.0.0",
|
||||
"_where": "/Users/trott/io.js/tools/eslint-tmp/node_modules/eslint/node_modules/espree",
|
||||
"_where": "/Users/apapirovski/Web/nodejs/tools/eslint-tmp/node_modules/eslint/node_modules/espree",
|
||||
"bugs": {
|
||||
"url": "https://github.com/RReverser/acorn-jsx/issues"
|
||||
},
|
||||
|
||||
+1
@@ -59,6 +59,7 @@ r-e-d
|
||||
Richard Gibson
|
||||
Rich Harris
|
||||
Sebastian McKenzie
|
||||
Shahar Soel
|
||||
Simen Bekkhus
|
||||
Teddy Katz
|
||||
Timothy Gu
|
||||
|
||||
File mode changed.
+9
-10
@@ -183,15 +183,15 @@ var types = {
|
||||
eq: new TokenType("=", {beforeExpr: true, isAssign: true}),
|
||||
assign: new TokenType("_=", {beforeExpr: true, isAssign: true}),
|
||||
incDec: new TokenType("++/--", {prefix: true, postfix: true, startsExpr: true}),
|
||||
prefix: new TokenType("prefix", {beforeExpr: true, prefix: true, startsExpr: true}),
|
||||
prefix: new TokenType("!/~", {beforeExpr: true, prefix: true, startsExpr: true}),
|
||||
logicalOR: binop("||", 1),
|
||||
logicalAND: binop("&&", 2),
|
||||
bitwiseOR: binop("|", 3),
|
||||
bitwiseXOR: binop("^", 4),
|
||||
bitwiseAND: binop("&", 5),
|
||||
equality: binop("==/!=", 6),
|
||||
relational: binop("</>", 7),
|
||||
bitShift: binop("<</>>", 8),
|
||||
equality: binop("==/!=/===/!==", 6),
|
||||
relational: binop("</>/<=/>=", 7),
|
||||
bitShift: binop("<</>>/>>>", 8),
|
||||
plusMin: new TokenType("+/-", {beforeExpr: true, binop: 9, prefix: true, startsExpr: true}),
|
||||
modulo: binop("%", 10),
|
||||
star: binop("*", 10),
|
||||
@@ -541,7 +541,7 @@ var pp = Parser.prototype;
|
||||
|
||||
// ## Parser utilities
|
||||
|
||||
var literal = /^(?:'((?:[^']|\.)*)'|"((?:[^"]|\.)*)"|;)/;
|
||||
var literal = /^(?:'((?:\\.|[^'])*?)'|"((?:\\.|[^"])*?)"|;)/;
|
||||
pp.strictDirective = function(start) {
|
||||
var this$1 = this;
|
||||
|
||||
@@ -2255,7 +2255,7 @@ pp$3.parseTemplate = function(ref) {
|
||||
|
||||
pp$3.isAsyncProp = function(prop) {
|
||||
return !prop.computed && prop.key.type === "Identifier" && prop.key.name === "async" &&
|
||||
(this.type === types.name || this.type === types.num || this.type === types.string || this.type === types.bracketL) &&
|
||||
(this.type === types.name || this.type === types.num || this.type === types.string || this.type === types.bracketL || this.type.keyword) &&
|
||||
!lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
|
||||
};
|
||||
|
||||
@@ -3131,7 +3131,7 @@ pp$8.readToken_caret = function() { // '^'
|
||||
pp$8.readToken_plus_min = function(code) { // '+-'
|
||||
var next = this.input.charCodeAt(this.pos + 1);
|
||||
if (next === code) {
|
||||
if (next == 45 && this.input.charCodeAt(this.pos + 2) == 62 &&
|
||||
if (next == 45 && !this.inModule && this.input.charCodeAt(this.pos + 2) == 62 &&
|
||||
(this.lastTokEnd === 0 || lineBreak.test(this.input.slice(this.lastTokEnd, this.pos)))) {
|
||||
// A `-->` line comment
|
||||
this.skipLineComment(3);
|
||||
@@ -3152,9 +3152,8 @@ pp$8.readToken_lt_gt = function(code) { // '<>'
|
||||
if (this.input.charCodeAt(this.pos + size) === 61) { return this.finishOp(types.assign, size + 1) }
|
||||
return this.finishOp(types.bitShift, size)
|
||||
}
|
||||
if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 &&
|
||||
if (next == 33 && code == 60 && !this.inModule && this.input.charCodeAt(this.pos + 2) == 45 &&
|
||||
this.input.charCodeAt(this.pos + 3) == 45) {
|
||||
if (this.inModule) { this.unexpected(); }
|
||||
// `<!--`, an XML-style comment that should be interpreted as a line comment
|
||||
this.skipLineComment(4);
|
||||
this.skipSpace();
|
||||
@@ -3651,7 +3650,7 @@ pp$8.readWord = function() {
|
||||
// [dammit]: acorn_loose.js
|
||||
// [walk]: util/walk.js
|
||||
|
||||
var version = "5.1.1";
|
||||
var version = "5.1.2";
|
||||
|
||||
// The main exported interface (under `self.acorn` when in the
|
||||
// browser) is a `parse` function that takes a code string and
|
||||
|
||||
+9
-10
@@ -189,15 +189,15 @@ var types = {
|
||||
eq: new TokenType("=", {beforeExpr: true, isAssign: true}),
|
||||
assign: new TokenType("_=", {beforeExpr: true, isAssign: true}),
|
||||
incDec: new TokenType("++/--", {prefix: true, postfix: true, startsExpr: true}),
|
||||
prefix: new TokenType("prefix", {beforeExpr: true, prefix: true, startsExpr: true}),
|
||||
prefix: new TokenType("!/~", {beforeExpr: true, prefix: true, startsExpr: true}),
|
||||
logicalOR: binop("||", 1),
|
||||
logicalAND: binop("&&", 2),
|
||||
bitwiseOR: binop("|", 3),
|
||||
bitwiseXOR: binop("^", 4),
|
||||
bitwiseAND: binop("&", 5),
|
||||
equality: binop("==/!=", 6),
|
||||
relational: binop("</>", 7),
|
||||
bitShift: binop("<</>>", 8),
|
||||
equality: binop("==/!=/===/!==", 6),
|
||||
relational: binop("</>/<=/>=", 7),
|
||||
bitShift: binop("<</>>/>>>", 8),
|
||||
plusMin: new TokenType("+/-", {beforeExpr: true, binop: 9, prefix: true, startsExpr: true}),
|
||||
modulo: binop("%", 10),
|
||||
star: binop("*", 10),
|
||||
@@ -547,7 +547,7 @@ var pp = Parser.prototype;
|
||||
|
||||
// ## Parser utilities
|
||||
|
||||
var literal = /^(?:'((?:[^']|\.)*)'|"((?:[^"]|\.)*)"|;)/;
|
||||
var literal = /^(?:'((?:\\.|[^'])*?)'|"((?:\\.|[^"])*?)"|;)/;
|
||||
pp.strictDirective = function(start) {
|
||||
var this$1 = this;
|
||||
|
||||
@@ -2261,7 +2261,7 @@ pp$3.parseTemplate = function(ref) {
|
||||
|
||||
pp$3.isAsyncProp = function(prop) {
|
||||
return !prop.computed && prop.key.type === "Identifier" && prop.key.name === "async" &&
|
||||
(this.type === types.name || this.type === types.num || this.type === types.string || this.type === types.bracketL) &&
|
||||
(this.type === types.name || this.type === types.num || this.type === types.string || this.type === types.bracketL || this.type.keyword) &&
|
||||
!lineBreak.test(this.input.slice(this.lastTokEnd, this.start))
|
||||
};
|
||||
|
||||
@@ -3137,7 +3137,7 @@ pp$8.readToken_caret = function() { // '^'
|
||||
pp$8.readToken_plus_min = function(code) { // '+-'
|
||||
var next = this.input.charCodeAt(this.pos + 1);
|
||||
if (next === code) {
|
||||
if (next == 45 && this.input.charCodeAt(this.pos + 2) == 62 &&
|
||||
if (next == 45 && !this.inModule && this.input.charCodeAt(this.pos + 2) == 62 &&
|
||||
(this.lastTokEnd === 0 || lineBreak.test(this.input.slice(this.lastTokEnd, this.pos)))) {
|
||||
// A `-->` line comment
|
||||
this.skipLineComment(3);
|
||||
@@ -3158,9 +3158,8 @@ pp$8.readToken_lt_gt = function(code) { // '<>'
|
||||
if (this.input.charCodeAt(this.pos + size) === 61) { return this.finishOp(types.assign, size + 1) }
|
||||
return this.finishOp(types.bitShift, size)
|
||||
}
|
||||
if (next == 33 && code == 60 && this.input.charCodeAt(this.pos + 2) == 45 &&
|
||||
if (next == 33 && code == 60 && !this.inModule && this.input.charCodeAt(this.pos + 2) == 45 &&
|
||||
this.input.charCodeAt(this.pos + 3) == 45) {
|
||||
if (this.inModule) { this.unexpected(); }
|
||||
// `<!--`, an XML-style comment that should be interpreted as a line comment
|
||||
this.skipLineComment(4);
|
||||
this.skipSpace();
|
||||
@@ -3657,7 +3656,7 @@ pp$8.readWord = function() {
|
||||
// [dammit]: acorn_loose.js
|
||||
// [walk]: util/walk.js
|
||||
|
||||
var version = "5.1.1";
|
||||
var version = "5.1.2";
|
||||
|
||||
// The main exported interface (under `self.acorn` when in the
|
||||
// browser) is a `parse` function that takes a code string and
|
||||
|
||||
+14
-11
@@ -1,27 +1,27 @@
|
||||
{
|
||||
"_from": "acorn@^5.0.1",
|
||||
"_id": "acorn@5.1.1",
|
||||
"_from": "acorn@^5.1.1",
|
||||
"_id": "acorn@5.1.2",
|
||||
"_inBundle": false,
|
||||
"_integrity": "sha512-vOk6uEMctu0vQrvuSqFdJyqj1Q0S5VTDL79qtjo+DhRr+1mmaD+tluFSCZqhvi/JUhXSzoZN2BhtstaPEeE8cw==",
|
||||
"_integrity": "sha512-o96FZLJBPY1lvTuJylGA9Bk3t/GKPPJG8H0ydQQl01crzwJgspa4AEIq/pVTXigmK0PHVQhiAtn8WMBLL9D2WA==",
|
||||
"_location": "/eslint/acorn",
|
||||
"_phantomChildren": {},
|
||||
"_requested": {
|
||||
"type": "range",
|
||||
"registry": true,
|
||||
"raw": "acorn@^5.0.1",
|
||||
"raw": "acorn@^5.1.1",
|
||||
"name": "acorn",
|
||||
"escapedName": "acorn",
|
||||
"rawSpec": "^5.0.1",
|
||||
"rawSpec": "^5.1.1",
|
||||
"saveSpec": null,
|
||||
"fetchSpec": "^5.0.1"
|
||||
"fetchSpec": "^5.1.1"
|
||||
},
|
||||
"_requiredBy": [
|
||||
"/eslint/espree"
|
||||
],
|
||||
"_resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.1.tgz",
|
||||
"_shasum": "53fe161111f912ab999ee887a90a0bc52822fd75",
|
||||
"_spec": "acorn@^5.0.1",
|
||||
"_where": "/Users/trott/io.js/tools/eslint-tmp/node_modules/eslint/node_modules/espree",
|
||||
"_resolved": "https://registry.npmjs.org/acorn/-/acorn-5.1.2.tgz",
|
||||
"_shasum": "911cb53e036807cf0fa778dc5d370fbd864246d7",
|
||||
"_spec": "acorn@^5.1.1",
|
||||
"_where": "/Users/apapirovski/Web/nodejs/tools/eslint-tmp/node_modules/eslint/node_modules/espree",
|
||||
"bin": {
|
||||
"acorn": "./bin/acorn"
|
||||
},
|
||||
@@ -210,6 +210,9 @@
|
||||
{
|
||||
"name": "Sebastian McKenzie"
|
||||
},
|
||||
{
|
||||
"name": "Shahar Soel"
|
||||
},
|
||||
{
|
||||
"name": "Simen Bekkhus"
|
||||
},
|
||||
@@ -279,5 +282,5 @@
|
||||
"pretest": "npm run build:main && npm run build:loose",
|
||||
"test": "node test/run.js && node test/lint.js"
|
||||
},
|
||||
"version": "5.1.1"
|
||||
"version": "5.1.2"
|
||||
}
|
||||
+283
-70
@@ -1,11 +1,13 @@
|
||||
# ajv-keywords
|
||||
|
||||
Custom JSON-Schema keywords for [ajv](https://github.com/epoberezkin/ajv) validator
|
||||
Custom JSON-Schema keywords for [Ajv](https://github.com/epoberezkin/ajv) validator
|
||||
|
||||
[](https://travis-ci.org/epoberezkin/ajv-keywords)
|
||||
[](https://www.npmjs.com/package/ajv-keywords)
|
||||
[](https://www.npmjs.com/package/ajv-keywords)
|
||||
[](https://coveralls.io/github/epoberezkin/ajv-keywords?branch=master)
|
||||
[](https://greenkeeper.io/)
|
||||
[](https://gitter.im/ajv-validator/ajv)
|
||||
|
||||
|
||||
## Contents
|
||||
@@ -16,12 +18,16 @@ Custom JSON-Schema keywords for [ajv](https://github.com/epoberezkin/ajv) valida
|
||||
- [typeof](#typeof)
|
||||
- [instanceof](#instanceof)
|
||||
- [range and exclusiveRange](#range-and-exclusiverange)
|
||||
- [propertyNames](#propertynames)
|
||||
- [if/then/else](#ifthenelse)
|
||||
- [switch](#switch)
|
||||
- [select/selectCases/selectDefault](#selectselectcasesselectdefault) (BETA)
|
||||
- [patternRequired](#patternrequired)
|
||||
- [prohibited](#prohibited)
|
||||
- [deepProperties](#deepproperties)
|
||||
- [deepRequired](#deeprequired)
|
||||
- [uniqueItemProperties](#uniqueitemproperties)
|
||||
- [regexp](#regexp)
|
||||
- [formatMaximum / formatMinimum and formatExclusiveMaximum / formatExclusiveMinimum](#formatmaximum--formatminimum-and-formatexclusivemaximum--formatexclusiveminimum)
|
||||
- [dynamicDefaults](#dynamicdefaults)
|
||||
- [License](#license)
|
||||
|
||||
@@ -93,7 +99,7 @@ To pass validation the result of `data instanceof ...` operation on the value sh
|
||||
```
|
||||
ajv.validate({ instanceof: 'Array' }, []); // true
|
||||
ajv.validate({ instanceof: 'Array' }, {}); // false
|
||||
ajv.validate({ instanceof: ['Array', 'Function'] }, funciton(){}); // true
|
||||
ajv.validate({ instanceof: ['Array', 'Function'] }, function(){}); // true
|
||||
```
|
||||
|
||||
You can add your own constructor function to be recognised by this keyword:
|
||||
@@ -133,40 +139,9 @@ ajv.validate(schema, 3); // false
|
||||
```
|
||||
|
||||
|
||||
### `propertyNames`
|
||||
|
||||
This keyword allows to define the schema for the property names of the object. The value of this keyword should be a valid JSON schema (v5 schemas are supported with Ajv option `{v5: true}`).
|
||||
|
||||
```javascript
|
||||
var schema = {
|
||||
type: 'object'
|
||||
propertyNames: {
|
||||
anyOf: [
|
||||
{ format: ipv4 },
|
||||
{ format: hostname }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var validData = {
|
||||
'192.128.0.1': {},
|
||||
'test.example.com': {}
|
||||
};
|
||||
|
||||
var invalidData = {
|
||||
'1.2.3': {}
|
||||
};
|
||||
|
||||
ajv.validate(schema, validData); // true
|
||||
ajv.validate(schema, invalidData); // false
|
||||
```
|
||||
|
||||
__Please note__: This keyword will be added to the next version of the JSON-Schema standard (draft-6), after it is published the keyword will be included in Ajv as standard validation keyword.
|
||||
|
||||
|
||||
### `if`/`then`/`else`
|
||||
|
||||
These keywords allow to implement conditional validation. Their values should be valid JSON-schemas. At the moment it requires using Ajv with v5 option.
|
||||
These keywords allow to implement conditional validation. Their values should be valid JSON-schemas.
|
||||
|
||||
If the data is valid according to the sub-schema in `if` keyword, then the result is equal to the result of data validation against the sub-schema in `then` keyword, otherwise - in `else` keyword (if `else` is absent, the validation succeeds).
|
||||
|
||||
@@ -195,6 +170,171 @@ ajv.validate(schema, invalidItems); // false
|
||||
This keyword is [proposed](https://github.com/json-schema-org/json-schema-spec/issues/180) for the future version of JSON-Schema standard.
|
||||
|
||||
|
||||
### `switch`
|
||||
|
||||
This keyword allows to perform advanced conditional validation.
|
||||
|
||||
The value of the keyword is the array of if/then clauses. Each clause is the object with the following properties:
|
||||
|
||||
- `if` (optional) - the value is JSON-schema
|
||||
- `then` (required) - the value is JSON-schema or boolean
|
||||
- `continue` (optional) - the value is boolean
|
||||
|
||||
The validation process is dynamic; all clauses are executed sequentially in the following way:
|
||||
|
||||
1. `if`:
|
||||
1. `if` property is JSON-schema according to which the data is:
|
||||
1. valid => go to step 2.
|
||||
2. invalid => go to the NEXT clause, if this was the last clause the validation of `switch` SUCCEEDS.
|
||||
2. `if` property is absent => go to step 2.
|
||||
2. `then`:
|
||||
1. `then` property is `true` or it is JSON-schema according to which the data is valid => go to step 3.
|
||||
2. `then` property is `false` or it is JSON-schema according to which the data is invalid => the validation of `switch` FAILS.
|
||||
3. `continue`:
|
||||
1. `continue` property is `true` => go to the NEXT clause, if this was the last clause the validation of `switch` SUCCEEDS.
|
||||
2. `continue` property is `false` or absent => validation of `switch` SUCCEEDS.
|
||||
|
||||
```javascript
|
||||
require('ajv-keywords')(ajv, 'switch');
|
||||
|
||||
var schema = {
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'integer',
|
||||
'switch': [
|
||||
{ if: { not: { minimum: 1 } }, then: false },
|
||||
{ if: { maximum: 10 }, then: true },
|
||||
{ if: { maximum: 100 }, then: { multipleOf: 10 } },
|
||||
{ if: { maximum: 1000 }, then: { multipleOf: 100 } },
|
||||
{ then: false }
|
||||
]
|
||||
}
|
||||
};
|
||||
|
||||
var validItems = [1, 5, 10, 20, 50, 100, 200, 500, 1000];
|
||||
|
||||
var invalidItems = [1, 0, 2000, 11, 57, 123, 'foo'];
|
||||
```
|
||||
|
||||
__Please note__: this keyword is moved here from Ajv, mainly to preserve beckward compatibility. It is unlikely to become a standard. It's preferreable to use `if`/`then`/`else` keywords if possible, as they are likely to be added to the standard. The above schema is equivalent to (for example):
|
||||
|
||||
```javascript
|
||||
{
|
||||
type: 'array',
|
||||
items: {
|
||||
type: 'integer',
|
||||
if: { minimum: 1, maximum: 10 },
|
||||
then: true,
|
||||
else: {
|
||||
if: { maximum: 100 },
|
||||
then: { multipleOf: 10 },
|
||||
else: {
|
||||
if: { maximum: 1000 },
|
||||
then: { multipleOf: 100 },
|
||||
else: false
|
||||
}
|
||||
}
|
||||
}
|
||||
}
|
||||
```
|
||||
|
||||
|
||||
### `select`/`selectCases`/`selectDefault`
|
||||
|
||||
These keywords allow to choose the schema to validate the data based on the value of some property in the validated data.
|
||||
|
||||
These keywords must be present in the same schema object (`selectDefault` is optional).
|
||||
|
||||
The value of `select` keyword should be a [$data reference](https://github.com/epoberezkin/ajv/tree/5.0.2-beta.0#data-reference) that points to any primitive JSON type (string, number, boolean or null) in the data that is validated. You can also use a constant of primitive type as the value of this keyword (e.g., for debugging purposes).
|
||||
|
||||
The value of `selectCases` keyword must be an object where each property name is a possible string representation of the value of `select` keyword and each property value is a corresponding schema (from draft-06 it can be boolean) that must be used to validate the data.
|
||||
|
||||
The value of `selectDefault` keyword is a schema (from draft-06 it can be boolean) that must be used to validate the data in case `selectCases` has no key equal to the stringified value of `select` keyword.
|
||||
|
||||
The validation succeeds in one of the following cases:
|
||||
- the validation of data using selected schema succeeds,
|
||||
- none of the schemas is selected for validation,
|
||||
- the value of select is undefined (no property in the data that the data reference points to).
|
||||
|
||||
If `select` value (in data) is not a primitive type the validation fails.
|
||||
|
||||
__Please note__: these keywords require Ajv `$data` option to support [$data reference](https://github.com/epoberezkin/ajv/tree/5.0.2-beta.0#data-reference).
|
||||
|
||||
|
||||
```javascript
|
||||
require('ajv-keywords')(ajv, 'select');
|
||||
|
||||
var schema = {
|
||||
type: object,
|
||||
required: ['kind'],
|
||||
properties: {
|
||||
kind: { type: 'string' }
|
||||
},
|
||||
select: { $data: '0/kind' },
|
||||
selectCases: {
|
||||
foo: {
|
||||
required: ['foo'],
|
||||
properties: {
|
||||
kind: {},
|
||||
foo: { type: 'string' }
|
||||
},
|
||||
additionalProperties: false
|
||||
},
|
||||
bar: {
|
||||
required: ['bar'],
|
||||
properties: {
|
||||
kind: {},
|
||||
bar: { type: 'number' }
|
||||
},
|
||||
additionalProperties: false
|
||||
}
|
||||
},
|
||||
selectDefault: {
|
||||
propertyNames: {
|
||||
not: { enum: ['foo', 'bar'] }
|
||||
}
|
||||
}
|
||||
};
|
||||
|
||||
var validDataList = [
|
||||
{ kind: 'foo', foo: 'any' },
|
||||
{ kind: 'bar', bar: 1 },
|
||||
{ kind: 'anything_else', not_bar_or_foo: 'any value' }
|
||||
];
|
||||
|
||||
var invalidDataList = [
|
||||
{ kind: 'foo' }, // no propery foo
|
||||
{ kind: 'bar' }, // no propery bar
|
||||
{ kind: 'foo', foo: 'any', another: 'any value' }, // additional property
|
||||
{ kind: 'bar', bar: 1, another: 'any value' }, // additional property
|
||||
{ kind: 'anything_else', foo: 'any' } // property foo not allowed
|
||||
{ kind: 'anything_else', bar: 1 } // property bar not allowed
|
||||
];
|
||||
```
|
||||
|
||||
__Please note__: the current implementation is BETA. It does not allow using relative URIs in $ref keywords in schemas in `selectCases` and `selectDefault` that point ouside of these schemas. The workaround is to use absolute URIs (that can point to any (sub-)schema added to Ajv, including those inside the current root schema where `select` is used). See [tests](https://github.com/epoberezkin/ajv-keywords/blob/v2.0.0/spec/tests/select.json#L314).
|
||||
|
||||
|
||||
### `patternRequired`
|
||||
|
||||
This keyword allows to require the presense of properties that match some pattern(s).
|
||||
|
||||
This keyword applies only to objects. If the data is not an object, the validation succeeds.
|
||||
|
||||
The value of this keyword should be an array of strings, each string being a regular expression. For data object to be valid each regular expression in this array should match at least one property name in the data object.
|
||||
|
||||
If the array contains multiple regular expressions, more than one expression can match the same property name.
|
||||
|
||||
```javascript
|
||||
var schema = { patternRequired: [ 'f.*o', 'b.*r' ] };
|
||||
|
||||
var validData = { foo: 1, bar: 2 };
|
||||
var alsoValidData = { foobar: 3 };
|
||||
|
||||
var invalidDataList = [ {}, { foo: 1 }, { bar: 2 } ];
|
||||
```
|
||||
|
||||
|
||||
### `prohibited`
|
||||
|
||||
This keyword allows to prohibit that any of the properties in the list is present in the object.
|
||||
@@ -217,42 +357,13 @@ var invalidDataList = [
|
||||
```
|
||||
|
||||
|
||||
### `deepRequired`
|
||||
### `deepProperties`
|
||||
|
||||
This keyword allows to check that some deep properties (identified by JSON pointers) are available. The value should be an array of JSON pointers to the data, starting from the current position in data.
|
||||
This keyword allows to validate deep properties (identified by JSON pointers).
|
||||
|
||||
```javascript
|
||||
var schema = {
|
||||
type: 'object',
|
||||
deepRequired: ["/users/1/role"]
|
||||
};
|
||||
This keyword applies only to objects. If the data is not an object, the validation succeeds.
|
||||
|
||||
var validData = {
|
||||
users: [
|
||||
{},
|
||||
{
|
||||
id: 123,
|
||||
role: 'admin'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var invalidData = {
|
||||
users: [
|
||||
{},
|
||||
{
|
||||
id: 123
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
See [json-schema-org/json-schema-spec#203](https://github.com/json-schema-org/json-schema-spec/issues/203#issue-197211916) for an example of the equivalent schema without `deepRequired` keyword.
|
||||
|
||||
|
||||
## `deepProperties`
|
||||
|
||||
This keyword allows to validate deep properties (identified by JSON pointers). The value should be an object, where keys are JSON pointers to the data, starting from the current position in data, and the values are corresponding schemas.
|
||||
The value should be an object, where keys are JSON pointers to the data, starting from the current position in data, and the values are JSON schemas. For data object to be valid the value of each JSON pointer should be valid according to the corresponding schema.
|
||||
|
||||
```javascript
|
||||
var schema = {
|
||||
@@ -302,9 +413,83 @@ var alsoInvalidData = {
|
||||
```
|
||||
|
||||
|
||||
### `deepRequired`
|
||||
|
||||
This keyword allows to check that some deep properties (identified by JSON pointers) are available.
|
||||
|
||||
This keyword applies only to objects. If the data is not an object, the validation succeeds.
|
||||
|
||||
The value should be an array of JSON pointers to the data, starting from the current position in data. For data object to be valid each JSON pointer should be some existing part of the data.
|
||||
|
||||
```javascript
|
||||
var schema = {
|
||||
type: 'object',
|
||||
deepRequired: ["/users/1/role"]
|
||||
};
|
||||
|
||||
var validData = {
|
||||
users: [
|
||||
{},
|
||||
{
|
||||
id: 123,
|
||||
role: 'admin'
|
||||
}
|
||||
]
|
||||
};
|
||||
|
||||
var invalidData = {
|
||||
users: [
|
||||
{},
|
||||
{
|
||||
id: 123
|
||||
}
|
||||
]
|
||||
};
|
||||
```
|
||||
|
||||
See [json-schema-org/json-schema-spec#203](https://github.com/json-schema-org/json-schema-spec/issues/203#issue-197211916) for an example of the equivalent schema without `deepRequired` keyword.
|
||||
|
||||
|
||||
### `uniqueItemProperties`
|
||||
|
||||
The keyword allows to check that some properties in array items are unique.
|
||||
|
||||
This keyword applies only to arrays. If the data is not an array, the validation succeeds.
|
||||
|
||||
The value of this keyword must be an array of strings - property names that should have unique values accross all items.
|
||||
|
||||
```javascript
|
||||
var schema = { uniqueItemProperties: [ "id", "name" ] };
|
||||
|
||||
var validData = [
|
||||
{ id: 1 },
|
||||
{ id: 2 },
|
||||
{ id: 3 }
|
||||
];
|
||||
|
||||
var invalidData1 = [
|
||||
{ id: 1 },
|
||||
{ id: 1 },
|
||||
{ id: 3 }
|
||||
];
|
||||
|
||||
var invalidData2 = [
|
||||
{ id: 1, name: "taco" },
|
||||
{ id: 2, name: "taco" }, // duplicate "name"
|
||||
{ id: 3, name: "salsa" }
|
||||
];
|
||||
```
|
||||
|
||||
This keyword is contributed by [@blainesch](https://github.com/blainesch).
|
||||
|
||||
|
||||
### `regexp`
|
||||
|
||||
This keyword allows to use regular expressions with flags in schemas (the standard `pattern` keyword does not support flags). The value of this keyword can be either a string (the result of `regexp.toString()`) or an object with the properties `pattern` and `flags` (the same strings that should be passed to RegExp constructor).
|
||||
This keyword allows to use regular expressions with flags in schemas (the standard `pattern` keyword does not support flags).
|
||||
|
||||
This keyword applies only to strings. If the data is not a string, the validation succeeds.
|
||||
|
||||
The value of this keyword can be either a string (the result of `regexp.toString()`) or an object with the properties `pattern` and `flags` (the same strings that should be passed to RegExp constructor).
|
||||
|
||||
```javascript
|
||||
var schema = {
|
||||
@@ -327,6 +512,34 @@ var invalidData = {
|
||||
```
|
||||
|
||||
|
||||
### `formatMaximum` / `formatMinimum` and `formatExclusiveMaximum` / `formatExclusiveMinimum`
|
||||
|
||||
These keywords allow to define minimum/maximum constraints when the format keyword defines ordering.
|
||||
|
||||
These keywords apply only to strings. If the data is not a string, the validation succeeds.
|
||||
|
||||
The value of keyword `formatMaximum` (`formatMinimum`) should be a string. This value is the maximum (minimum) allowed value for the data to be valid as determined by `format` keyword.
|
||||
|
||||
When this keyword is added, it defines comparison rules for formats `"date"`, `"time"` and `"date-time". Custom formats also can have comparison rules. See [addFormat](https://github.com/epoberezkin/ajv#api-addformat) method.
|
||||
|
||||
The value of keyword `formatExclusiveMaximum` (`formatExclusiveMinimum`) should be a boolean value. These keyword cannot be used without `formatMaximum` (`formatMinimum`). If this keyword value is equal to `true`, the data to be valid should not be equal to the value in `formatMaximum` (`formatMinimum`) keyword.
|
||||
|
||||
```javascript
|
||||
require('ajv-keywords')(ajv, ['formatMinimum', 'formatMaximum']);
|
||||
|
||||
var schema = {
|
||||
format: 'date',
|
||||
formatMinimum: '2016-02-06',
|
||||
formatMaximum: '2016-12-27',
|
||||
formatExclusiveMaximum: true
|
||||
}
|
||||
|
||||
var validDataList = ['2016-02-06', '2016-12-26', 1];
|
||||
|
||||
var invalidDataList = ['2016-02-05', '2016-12-27', 'abc'];
|
||||
```
|
||||
|
||||
|
||||
### `dynamicDefaults`
|
||||
|
||||
This keyword allows to assign dynamic defaults to properties, such as timestamps, unique IDs etc.
|
||||
@@ -440,4 +653,4 @@ var schema = {
|
||||
|
||||
## License
|
||||
|
||||
[MIT](https://github.com/JSONScript/ajv-keywords/blob/master/LICENSE)
|
||||
[MIT](https://github.com/epoberezkin/ajv-keywords/blob/master/LICENSE)
|
||||
+2
-4
@@ -12,9 +12,6 @@ var COMPARE_FORMATS = {
|
||||
module.exports = function (minMax) {
|
||||
var keyword = 'format' + minMax;
|
||||
return function defFunc(ajv) {
|
||||
if (ajv.RULES.keywords[keyword])
|
||||
return console.warn('Keyword', keyword, 'is already defined');
|
||||
|
||||
defFunc.definition = {
|
||||
type: 'string',
|
||||
inline: require('./dotjs/_formatLimit'),
|
||||
@@ -53,7 +50,8 @@ function extendFormats(ajv) {
|
||||
var formats = ajv._formats;
|
||||
for (var name in COMPARE_FORMATS) {
|
||||
var format = formats[name];
|
||||
if (typeof format != 'object')
|
||||
// the last condition is needed if it's RegExp from another window
|
||||
if (typeof format != 'object' || format instanceof RegExp || !format.validate)
|
||||
format = formats[name] = { validate: format };
|
||||
if (!format.compare)
|
||||
format.compare = COMPARE_FORMATS[name];
|
||||
|
||||
+15
@@ -0,0 +1,15 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = {
|
||||
metaSchemaRef: metaSchemaRef
|
||||
};
|
||||
|
||||
var META_SCHEMA_ID = 'http://json-schema.org/draft-06/schema';
|
||||
|
||||
function metaSchemaRef(ajv) {
|
||||
var defaultMeta = ajv._opts.defaultMeta;
|
||||
if (typeof defaultMeta == 'string') return { $ref: defaultMeta };
|
||||
if (ajv.getSchema(META_SCHEMA_ID)) return { $ref: META_SCHEMA_ID };
|
||||
console.warn('meta schema not defined');
|
||||
return {};
|
||||
}
|
||||
+6
-7
@@ -1,5 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
var util = require('./_util');
|
||||
|
||||
module.exports = function defFunc(ajv) {
|
||||
defFunc.definition = {
|
||||
type: 'object',
|
||||
@@ -11,14 +13,11 @@ module.exports = function defFunc(ajv) {
|
||||
},
|
||||
metaSchema: {
|
||||
type: 'object',
|
||||
patternProperties: {
|
||||
'^(\\/([^~\\/]|~0|~1)*)*(\\/)?$': {
|
||||
$ref: ajv._opts.v5
|
||||
? 'https://raw.githubusercontent.com/epoberezkin/ajv/master/lib/refs/json-schema-v5.json#'
|
||||
: 'http://json-schema.org/draft-04/schema#'
|
||||
}
|
||||
propertyNames: {
|
||||
type: 'string',
|
||||
format: 'json-pointer'
|
||||
},
|
||||
additionalProperties: false
|
||||
additionalProperties: util.metaSchemaRef(ajv)
|
||||
}
|
||||
};
|
||||
|
||||
|
||||
+2
-2
@@ -33,7 +33,7 @@ var {{=$valid}} = undefined;
|
||||
|
||||
{{
|
||||
var $schemaFormat = it.schema.format
|
||||
, $isDataFormat = it.opts.v5 && $schemaFormat.$data
|
||||
, $isDataFormat = it.opts.$data && $schemaFormat.$data
|
||||
, $closingBraces = '';
|
||||
}}
|
||||
|
||||
@@ -58,7 +58,7 @@ var {{=$valid}} = undefined;
|
||||
var $isMax = $keyword == 'formatMaximum'
|
||||
, $exclusiveKeyword = 'formatExclusive' + ($isMax ? 'Maximum' : 'Minimum')
|
||||
, $schemaExcl = it.schema[$exclusiveKeyword]
|
||||
, $isDataExcl = it.opts.v5 && $schemaExcl && $schemaExcl.$data
|
||||
, $isDataExcl = it.opts.$data && $schemaExcl && $schemaExcl.$data
|
||||
, $op = $isMax ? '<' : '>'
|
||||
, $result = 'result' + $lvl;
|
||||
}}
|
||||
|
||||
+7
-2
@@ -4,16 +4,21 @@
|
||||
|
||||
{{
|
||||
var $key = 'key' + $lvl
|
||||
, $idx = 'idx' + $lvl
|
||||
, $matched = 'patternMatched' + $lvl
|
||||
, $dataProperties = 'dataProperties' + $lvl
|
||||
, $closingBraces = ''
|
||||
, $ownProperties = it.opts.ownProperties;
|
||||
}}
|
||||
|
||||
var {{=$valid}} = true;
|
||||
{{? $ownProperties }}
|
||||
var {{=$dataProperties}} = undefined;
|
||||
{{?}}
|
||||
|
||||
{{~ $schema:$pProperty }}
|
||||
var {{=$matched}} = false;
|
||||
for (var {{=$key}} in {{=$data}}) {
|
||||
{{# def.checkOwnProperty }}
|
||||
{{# def.iterateProperties }}
|
||||
{{=$matched}} = {{= it.usePattern($pProperty) }}.test({{=$key}});
|
||||
if ({{=$matched}}) break;
|
||||
}
|
||||
|
||||
+3
-3
@@ -10,7 +10,7 @@
|
||||
{{# def._validateSwitchRule:if }}
|
||||
{{ $it.createErrors = true; }}
|
||||
{{# def.resetCompositeRule }}
|
||||
{{=$ifPassed}} = valid{{=$it.level}};
|
||||
{{=$ifPassed}} = {{=$nextValid}};
|
||||
#}}
|
||||
|
||||
{{## def.validateThen:
|
||||
@@ -18,7 +18,7 @@
|
||||
{{? $sch.then === false }}
|
||||
{{# def.error:'switch' }}
|
||||
{{?}}
|
||||
var valid{{=$it.level}} = {{= $sch.then }};
|
||||
var {{=$nextValid}} = {{= $sch.then }};
|
||||
{{??}}
|
||||
{{# def._validateSwitchRule:then }}
|
||||
{{?}}
|
||||
@@ -68,6 +68,6 @@ var {{=$ifPassed}};
|
||||
|
||||
{{= $closingBraces }}
|
||||
|
||||
var {{=$valid}} = valid{{=$it.level}};
|
||||
var {{=$valid}} = {{=$nextValid}};
|
||||
|
||||
{{# def.cleanUp }}
|
||||
+3
-3
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
module.exports = function generate__formatLimit(it, $keyword) {
|
||||
module.exports = function generate__formatLimit(it, $keyword, $ruleType) {
|
||||
var out = ' ';
|
||||
var $lvl = it.level;
|
||||
var $dataLvl = it.dataLevel;
|
||||
@@ -16,7 +16,7 @@ module.exports = function generate__formatLimit(it, $keyword) {
|
||||
return out;
|
||||
}
|
||||
var $schemaFormat = it.schema.format,
|
||||
$isDataFormat = it.opts.v5 && $schemaFormat.$data,
|
||||
$isDataFormat = it.opts.$data && $schemaFormat.$data,
|
||||
$closingBraces = '';
|
||||
if ($isDataFormat) {
|
||||
var $schemaValueFormat = it.util.getData($schemaFormat.$data, $dataLvl, it.dataPathArr),
|
||||
@@ -34,7 +34,7 @@ module.exports = function generate__formatLimit(it, $keyword) {
|
||||
var $isMax = $keyword == 'formatMaximum',
|
||||
$exclusiveKeyword = 'formatExclusive' + ($isMax ? 'Maximum' : 'Minimum'),
|
||||
$schemaExcl = it.schema[$exclusiveKeyword],
|
||||
$isDataExcl = it.opts.v5 && $schemaExcl && $schemaExcl.$data,
|
||||
$isDataExcl = it.opts.$data && $schemaExcl && $schemaExcl.$data,
|
||||
$op = $isMax ? '<' : '>',
|
||||
$result = 'result' + $lvl;
|
||||
var $isData = it.opts.$data && $schema && $schema.$data,
|
||||
|
||||
+11
-5
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
module.exports = function generate_patternRequired(it, $keyword) {
|
||||
module.exports = function generate_patternRequired(it, $keyword, $ruleType) {
|
||||
var out = ' ';
|
||||
var $lvl = it.level;
|
||||
var $dataLvl = it.dataLevel;
|
||||
@@ -7,29 +7,35 @@ module.exports = function generate_patternRequired(it, $keyword) {
|
||||
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
|
||||
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
|
||||
var $breakOnError = !it.opts.allErrors;
|
||||
var $errorKeyword;
|
||||
var $data = 'data' + ($dataLvl || '');
|
||||
var $valid = 'valid' + $lvl;
|
||||
var $key = 'key' + $lvl,
|
||||
$idx = 'idx' + $lvl,
|
||||
$matched = 'patternMatched' + $lvl,
|
||||
$dataProperties = 'dataProperties' + $lvl,
|
||||
$closingBraces = '',
|
||||
$ownProperties = it.opts.ownProperties;
|
||||
out += 'var ' + ($valid) + ' = true;';
|
||||
if ($ownProperties) {
|
||||
out += ' var ' + ($dataProperties) + ' = undefined;';
|
||||
}
|
||||
var arr1 = $schema;
|
||||
if (arr1) {
|
||||
var $pProperty, i1 = -1,
|
||||
l1 = arr1.length - 1;
|
||||
while (i1 < l1) {
|
||||
$pProperty = arr1[i1 += 1];
|
||||
out += ' var ' + ($matched) + ' = false; for (var ' + ($key) + ' in ' + ($data) + ') { ';
|
||||
out += ' var ' + ($matched) + ' = false; ';
|
||||
if ($ownProperties) {
|
||||
out += ' if (!Object.prototype.hasOwnProperty.call(' + ($data) + ', ' + ($key) + ')) continue; ';
|
||||
out += ' ' + ($dataProperties) + ' = ' + ($dataProperties) + ' || Object.keys(' + ($data) + '); for (var ' + ($idx) + '=0; ' + ($idx) + '<' + ($dataProperties) + '.length; ' + ($idx) + '++) { var ' + ($key) + ' = ' + ($dataProperties) + '[' + ($idx) + ']; ';
|
||||
} else {
|
||||
out += ' for (var ' + ($key) + ' in ' + ($data) + ') { ';
|
||||
}
|
||||
out += ' ' + ($matched) + ' = ' + (it.usePattern($pProperty)) + '.test(' + ($key) + '); if (' + ($matched) + ') break; } ';
|
||||
var $missingPattern = it.util.escapeQuotes($pProperty);
|
||||
out += ' if (!' + ($matched) + ') { ' + ($valid) + ' = false; var err = '; /* istanbul ignore else */
|
||||
if (it.createErrors !== false) {
|
||||
out += ' { keyword: \'' + ($errorKeyword || 'patternRequired') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingPattern: \'' + ($missingPattern) + '\' } ';
|
||||
out += ' { keyword: \'' + ('patternRequired') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { missingPattern: \'' + ($missingPattern) + '\' } ';
|
||||
if (it.opts.messages !== false) {
|
||||
out += ' , message: \'should have property matching pattern \\\'' + ($missingPattern) + '\\\'\' ';
|
||||
}
|
||||
|
||||
+7
-8
@@ -1,5 +1,5 @@
|
||||
'use strict';
|
||||
module.exports = function generate_switch(it, $keyword) {
|
||||
module.exports = function generate_switch(it, $keyword, $ruleType) {
|
||||
var out = ' ';
|
||||
var $lvl = it.level;
|
||||
var $dataLvl = it.dataLevel;
|
||||
@@ -7,7 +7,6 @@ module.exports = function generate_switch(it, $keyword) {
|
||||
var $schemaPath = it.schemaPath + it.util.getProperty($keyword);
|
||||
var $errSchemaPath = it.errSchemaPath + '/' + $keyword;
|
||||
var $breakOnError = !it.opts.allErrors;
|
||||
var $errorKeyword;
|
||||
var $data = 'data' + ($dataLvl || '');
|
||||
var $valid = 'valid' + $lvl;
|
||||
var $errs = 'errs__' + $lvl;
|
||||
@@ -41,14 +40,14 @@ module.exports = function generate_switch(it, $keyword) {
|
||||
$it.baseId = $currentBaseId;
|
||||
$it.createErrors = true;
|
||||
it.compositeRule = $it.compositeRule = $wasComposite;
|
||||
out += ' ' + ($ifPassed) + ' = valid' + ($it.level) + '; if (' + ($ifPassed) + ') { ';
|
||||
out += ' ' + ($ifPassed) + ' = ' + ($nextValid) + '; if (' + ($ifPassed) + ') { ';
|
||||
if (typeof $sch.then == 'boolean') {
|
||||
if ($sch.then === false) {
|
||||
var $$outStack = $$outStack || [];
|
||||
$$outStack.push(out);
|
||||
out = ''; /* istanbul ignore else */
|
||||
if (it.createErrors !== false) {
|
||||
out += ' { keyword: \'' + ($errorKeyword || 'switch') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { caseIndex: ' + ($caseIndex) + ' } ';
|
||||
out += ' { keyword: \'' + ('switch') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { caseIndex: ' + ($caseIndex) + ' } ';
|
||||
if (it.opts.messages !== false) {
|
||||
out += ' , message: \'should pass "switch" keyword validation\' ';
|
||||
}
|
||||
@@ -71,7 +70,7 @@ module.exports = function generate_switch(it, $keyword) {
|
||||
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
|
||||
}
|
||||
}
|
||||
out += ' var valid' + ($it.level) + ' = ' + ($sch.then) + '; ';
|
||||
out += ' var ' + ($nextValid) + ' = ' + ($sch.then) + '; ';
|
||||
} else {
|
||||
$it.schema = $sch.then;
|
||||
$it.schemaPath = $schemaPath + '[' + $caseIndex + '].then';
|
||||
@@ -88,7 +87,7 @@ module.exports = function generate_switch(it, $keyword) {
|
||||
$$outStack.push(out);
|
||||
out = ''; /* istanbul ignore else */
|
||||
if (it.createErrors !== false) {
|
||||
out += ' { keyword: \'' + ($errorKeyword || 'switch') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { caseIndex: ' + ($caseIndex) + ' } ';
|
||||
out += ' { keyword: \'' + ('switch') + '\' , dataPath: (dataPath || \'\') + ' + (it.errorPath) + ' , schemaPath: ' + (it.util.toQuotedString($errSchemaPath)) + ' , params: { caseIndex: ' + ($caseIndex) + ' } ';
|
||||
if (it.opts.messages !== false) {
|
||||
out += ' , message: \'should pass "switch" keyword validation\' ';
|
||||
}
|
||||
@@ -111,7 +110,7 @@ module.exports = function generate_switch(it, $keyword) {
|
||||
out += ' var err = ' + (__err) + '; if (vErrors === null) vErrors = [err]; else vErrors.push(err); errors++; ';
|
||||
}
|
||||
}
|
||||
out += ' var valid' + ($it.level) + ' = ' + ($sch.then) + '; ';
|
||||
out += ' var ' + ($nextValid) + ' = ' + ($sch.then) + '; ';
|
||||
} else {
|
||||
$it.schema = $sch.then;
|
||||
$it.schemaPath = $schemaPath + '[' + $caseIndex + '].then';
|
||||
@@ -123,7 +122,7 @@ module.exports = function generate_switch(it, $keyword) {
|
||||
$shouldContinue = $sch.continue
|
||||
}
|
||||
}
|
||||
out += '' + ($closingBraces) + 'var ' + ($valid) + ' = valid' + ($it.level) + '; ';
|
||||
out += '' + ($closingBraces) + 'var ' + ($valid) + ' = ' + ($nextValid) + '; ';
|
||||
out = it.util.cleanUpCode(out);
|
||||
return out;
|
||||
}
|
||||
+1
-1
@@ -1,7 +1,7 @@
|
||||
'use strict';
|
||||
|
||||
module.exports = function defFunc(ajv) {
|
||||
if (!ajv._opts.v5) console.warn('keywords if/then/else require v5 option');
|
||||
if (!ajv.RULES.keywords.switch) require('./switch')(ajv);
|
||||
|
||||
defFunc.definition = {
|
||||
macro: function (schema, parentSchema) {
|
||||
|
||||
Loaded 100 of 476 files, more files were not shown because too many files have changed in this diff.
Show more
Reference in new issue
Block a user