diff --git a/.github/ISSUE_TEMPLATE/BUG_REPORT.md b/.github/ISSUE_TEMPLATE/BUG_REPORT.md
index ef8c5101cd96..a614d6b2f5c7 100644
--- a/.github/ISSUE_TEMPLATE/BUG_REPORT.md
+++ b/.github/ISSUE_TEMPLATE/BUG_REPORT.md
@@ -1,5 +1,5 @@
---
-name: Bug report
+name: "\U0001F41E Bug report"
about: Report an issue with ESLint or rules bundled with ESLint
---
@@ -51,4 +51,10 @@ about: Report an issue with ESLint or rules bundled with ESLint
**What did you expect to happen?**
-**What actually happened? Please include the actual, raw output from ESLint.**
\ No newline at end of file
+
+**What actually happened? Please include the actual, raw output from ESLint.**
+
+
+**Are you willing to submit a pull request to fix this bug?**
+
+
diff --git a/.github/ISSUE_TEMPLATE/CHANGE.md b/.github/ISSUE_TEMPLATE/CHANGE.md
index 14cff91b0e06..3fe2fe241a7b 100644
--- a/.github/ISSUE_TEMPLATE/CHANGE.md
+++ b/.github/ISSUE_TEMPLATE/CHANGE.md
@@ -1,5 +1,5 @@
---
-name: Non-rule change request
+name: "\U0001F4DD Non-rule change request"
about: Request a change that is not a bug fix, rule change, or new rule
---
@@ -19,7 +19,12 @@ about: Request a change that is not a bug fix, rule change, or new rule
**The version of ESLint you are using.**
+
**The problem you want to solve.**
+
**Your take on the correct solution to problem.**
+
+**Are you willing to submit a pull request to implement this change?**
+
diff --git a/.github/ISSUE_TEMPLATE/NEW_RULE.md b/.github/ISSUE_TEMPLATE/NEW_RULE.md
index 454b28e4090e..8dfb93ba281b 100644
--- a/.github/ISSUE_TEMPLATE/NEW_RULE.md
+++ b/.github/ISSUE_TEMPLATE/NEW_RULE.md
@@ -1,5 +1,5 @@
---
-name: New rule proposal
+/name: "\U0001F680 New rule proposal"
about: Propose a new rule to be added to ESLint
---
@@ -35,3 +35,6 @@ about: Propose a new rule to be added to ESLint
**Why should this rule be included in ESLint (instead of a plugin)?**
+
+**Are you willing to submit a pull request to implement this rule?**
+
diff --git a/.github/ISSUE_TEMPLATE/QUESTION.md b/.github/ISSUE_TEMPLATE/QUESTION.md
new file mode 100644
index 000000000000..ba96b41044c9
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/QUESTION.md
@@ -0,0 +1,22 @@
+---
+name: "⛔ Question"
+about: Please go to https://gitter.im/eslint/eslint
+
+---
+
+
diff --git a/.github/ISSUE_TEMPLATE/RULE_CHANGE.md b/.github/ISSUE_TEMPLATE/RULE_CHANGE.md
index 7b0c6537971d..9ecb2d002b5d 100644
--- a/.github/ISSUE_TEMPLATE/RULE_CHANGE.md
+++ b/.github/ISSUE_TEMPLATE/RULE_CHANGE.md
@@ -1,5 +1,5 @@
---
-name: Rule change request
+name: "\U0001F4DD Rule change request"
about: Request a change to an existing rule
---
@@ -34,3 +34,4 @@ about: Request a change to an existing rule
**What will the rule do after it's changed?**
+**Are you willing to submit a pull request to implement this change?**
\ No newline at end of file
diff --git a/.github/ISSUE_TEMPLATE/SECURITY.md b/.github/ISSUE_TEMPLATE/SECURITY.md
new file mode 100644
index 000000000000..8cc33ec6ecaf
--- /dev/null
+++ b/.github/ISSUE_TEMPLATE/SECURITY.md
@@ -0,0 +1,19 @@
+---
+name: "⛔ Security issue"
+about: Please file security issues at https://hackerone.com/eslint
+
+---
+
+
diff --git a/.gitignore b/.gitignore
index eae2b21d1ab1..d2c98d47bd91 100644
--- a/.gitignore
+++ b/.gitignore
@@ -15,3 +15,4 @@ versions.json
/packages/**/node_modules
/.vscode
.sublimelinterrc
+.eslint-release-info.json
diff --git a/.travis.yml b/.travis.yml
index ff3e956a7025..22decd59f58d 100644
--- a/.travis.yml
+++ b/.travis.yml
@@ -4,6 +4,7 @@ node_js:
- "8"
- "9"
- "10"
+ - "11"
sudo: false
branches:
only:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 0a372690c2f4..f91359ef4c28 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,50 @@
+v5.8.0 - October 26, 2018
+
+* 9152417 Fix: deprecation warning in RuleTester using Node v11 (#11009) (Teddy Katz)
+* e349a03 Docs: Update issue templates to ask for PRs (#11012) (Nicholas C. Zakas)
+* 3d88b38 Chore: avoid using legacy report API in no-irregular-whitespace (#11013) (Teddy Katz)
+* 5a31a92 Build: compile espree's deps to ES5 when generating site (fixes #11014) (#11015) (Teddy Katz)
+* 3943635 Update: Create Linter.version API (fixes #9271) (#11010) (Nicholas C. Zakas)
+* a940cf4 Docs: Mention version for config glob patterns (fixes #8793) (Nicholas C. Zakas)
+* 6e1c530 Build: run tests on Node 11 (#11008) (Teddy Katz)
+* 58ff359 Docs: add instructions for npm 2FA (refs #10631) (#10992) (Teddy Katz)
+* 2f87bb3 Upgrade: eslint-release@1.0.0 (refs #10631) (#10991) (Teddy Katz)
+* 57ef0fd Fix: prefer-const when using destructuring assign (fixes #8308) (#10924) (Nicholas C. Zakas)
+* 577cbf1 Chore: Add typescript-specific edge case tests to space-infix-ops (#10986) (Bence Dányi)
+* d45b184 Chore: Using deconstruction assignment for shelljs (#10974) (ZYSzys)
+
+v5.7.0 - October 12, 2018
+
+* 6cb63fd Update: Add iife to padding-line-between-statements (fixes #10853) (#10916) (Kevin Partington)
+* 5fd1bda Update: no-tabs allowIndentationTabs option (fixes #10256) (#10925) (Kevin Partington)
+* d12be69 Fix: no-extra-bind No autofix if arg may have side effect (fixes #10846) (#10918) (Kevin Partington)
+* 847372f Fix: no-unused-vars false pos. with markVariableAsUsed (fixes #10952) (#10954) (Roy Sutton)
+* 4132de7 Chore: Simplify space-infix-ops (#10935) (Bence Dányi)
+* 543edfa Fix: Fix error with one-var (fixes #10937) (#10938) (Justin Krup)
+* 95c4cb1 Docs: Fix typo for no-unsafe-finally (#10945) (Sergio Santoro)
+* 5fe0e1a Fix: no-invalid-regexp disallows \ at end of pattern (fixes #10861) (#10920) (Toru Nagashima)
+* f85547a Docs: Add 'When Not To Use' section to space-infix-ops (#10931) (Bence Dányi)
+* 3dccac4 Docs: Update working-with-parsers link (#10929) (Azeem Bande-Ali)
+* 557a8bb Docs: Remove old note about caching, add a new one (fixes #10739) (#10913) (Zac)
+* fe8111a Chore: Add more test cases to space-infix-ops (#10936) (Bence Dányi)
+* 066f7e0 Update: camelcase rule ignoreList added (#10783) (Julien Martin)
+* 70bde69 Upgrade: table to version 5 (#10903) (Rouven Weßling)
+* 2e52bca Chore: Update issue templates (#10900) (Nicholas C. Zakas)
+
+v5.6.1 - September 28, 2018
+
+* 9b26bdb Fix: avoid exponential require-atomic-updates traversal (fixes #10893) (#10894) (Teddy Katz)
+* 9432b10 Fix: make separateRequires work in consecutive mode (fixes #10784) (#10886) (Pig Fang)
+* e51868d Upgrade: debug@4 (fixes #10854) (#10887) (薛定谔的猫)
+* d3f3994 Docs: add information about reporting security issues (#10889) (Teddy Katz)
+* cc458f4 Build: fix failing tests on master (#10890) (Teddy Katz)
+* a6ebfd3 Docs: clarify defaultAssignment option, fix no-unneeded-ternary examples (#10874) (CoffeeTableEspresso)
+* 9d52541 Fix: Remove duplicate error message on crash (fixes #8964) (#10865) (Nicholas C. Zakas)
+* 4eb9a49 Docs: Update quotes.md (#10862) (The Jared Wilcurt)
+* 9159e9b Docs: Update complexity.md (#10867) (Szymon Przybylski)
+* 14f4e46 Docs: Use Linter instead of linter in Nodejs API page (#10864) (Nicholas C. Zakas)
+* b3e3cb1 Chore: Update debug log name to match filename (#10863) (Nicholas C. Zakas)
+
v5.6.0 - September 14, 2018
* c5b688e Update: Added generators option to func-names (fixes #9511) (#10697) (Oscar Barrett)
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index 411e7fb523b4..d0db77ccfe63 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -15,6 +15,8 @@ Before filing an issue, please be sure to read the guidelines for what you're re
* [Proposing a Rule Change](https://eslint.org/docs/developer-guide/contributing/rule-changes)
* [Request a Change](https://eslint.org/docs/developer-guide/contributing/changes)
+To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint).
+
## Contributing Code
Please sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint) and read over the [Pull Request Guidelines](https://eslint.org/docs/developer-guide/contributing/pull-requests).
diff --git a/Makefile.js b/Makefile.js
index 3c514af096c8..473ffdf58872 100644
--- a/Makefile.js
+++ b/Makefile.js
@@ -23,23 +23,11 @@ const lodash = require("lodash"),
os = require("os"),
path = require("path"),
semver = require("semver"),
- shell = require("shelljs"),
ejs = require("ejs"),
loadPerf = require("load-perf"),
yaml = require("js-yaml");
-const cat = shell.cat;
-const cd = shell.cd;
-const cp = shell.cp;
-const echo = shell.echo;
-const exec = shell.exec;
-const exit = shell.exit;
-const find = shell.find;
-const ls = shell.ls;
-const mkdir = shell.mkdir;
-const pwd = shell.pwd;
-const rm = shell.rm;
-const test = shell.test;
+const { cat, cd, cp, echo, exec, exit, find, ls, mkdir, pwd, rm, test } = require("shelljs");
//------------------------------------------------------------------------------
// Settings
@@ -162,10 +150,11 @@ function execSilent(cmd) {
/**
* Generates a release blog post for eslint.org
* @param {Object} releaseInfo The release metadata.
+ * @param {string} [prereleaseMajorVersion] If this is a prerelease, the next major version after this prerelease
* @returns {void}
* @private
*/
-function generateBlogPost(releaseInfo) {
+function generateBlogPost(releaseInfo, prereleaseMajorVersion) {
const ruleList = ls("lib/rules")
// Strip the .js extension
@@ -178,7 +167,7 @@ function generateBlogPost(releaseInfo) {
*/
.sort((ruleA, ruleB) => ruleB.length - ruleA.length);
- const renderContext = Object.assign({ prereleaseMajorVersion: null, ruleList }, releaseInfo);
+ const renderContext = Object.assign({ prereleaseMajorVersion, ruleList }, releaseInfo);
const output = ejs.render(cat("./templates/blogpost.md.ejs"), renderContext),
now = new Date(),
@@ -262,11 +251,12 @@ function generateRuleIndexPage(basedir) {
}
/**
- * Commits the changes in the site and publishes them to GitHub.
- * @param {string} [tag] The string to tag the commit with.
+ * Creates a git commit and tag in an adjacent `eslint.github.io` repository, without pushing it to
+ * the remote. This assumes that the repository has already been modified somehow (e.g. by adding a blogpost).
+ * @param {string} [tag] The string to tag the commit with
* @returns {void}
*/
-function publishSite(tag) {
+function commitSiteToGit(tag) {
const currentDir = pwd();
cd(SITE_DIR);
@@ -278,39 +268,53 @@ function publishSite(tag) {
}
exec("git fetch origin && git rebase origin/master");
- exec("git push origin master --tags");
cd(currentDir);
}
/**
- * Creates a release version tag and pushes to origin.
- * @param {boolean} [ciRelease] Set to true to indicate this is a CI release.
+ * Publishes the changes in an adjacent `eslint.github.io` repository to the remote. The
+ * site should already have local commits (e.g. from running `commitSiteToGit`).
* @returns {void}
*/
-function release(ciRelease) {
+function publishSite() {
+ const currentDir = pwd();
+
+ cd(SITE_DIR);
+ exec("git push origin master --tags");
+ cd(currentDir);
+}
- const releaseInfo = ReleaseOps.release(null, ciRelease);
+/**
+ * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag,
+ * and generates the site in an adjacent `eslint.github.io` folder.
+ * @returns {void}
+ */
+function generateRelease() {
+ ReleaseOps.generateRelease();
+ const releaseInfo = JSON.parse(cat(".eslint-release-info.json"));
echo("Generating site");
target.gensite();
generateBlogPost(releaseInfo);
- publishSite(`v${releaseInfo.version}`);
- echo("Site has been published");
-
- echo("Publishing to GitHub");
- ReleaseOps.publishReleaseToGitHub(releaseInfo);
+ commitSiteToGit(`v${releaseInfo.version}`);
}
/**
- * Creates a prerelease version tag and pushes to origin.
+ * Updates the changelog, bumps the version number in package.json, creates a local git commit and tag,
+ * and generates the site in an adjacent `eslint.github.io` folder.
* @param {string} prereleaseId The prerelease identifier (alpha, beta, etc.)
* @returns {void}
*/
-function prerelease(prereleaseId) {
-
- const releaseInfo = ReleaseOps.release(prereleaseId);
+function generatePrerelease(prereleaseId) {
+ ReleaseOps.generateRelease(prereleaseId);
+ const releaseInfo = JSON.parse(cat(".eslint-release-info.json"));
const nextMajor = semver.inc(releaseInfo.version, "major");
+ echo("Generating site");
+
+ // always write docs into the next major directory (so 2.0.0-alpha.0 writes to 2.0.0)
+ target.gensite(nextMajor);
+
/*
* Premajor release should have identical "next major version".
* Preminor and prepatch release will not.
@@ -325,21 +329,23 @@ function prerelease(prereleaseId) {
* Blog post generation logic needs to be aware of this (as well as
* know what the next major version is actually supposed to be).
*/
- releaseInfo.prereleaseMajorVersion = nextMajor;
+ generateBlogPost(releaseInfo, nextMajor);
+ } else {
+ generateBlogPost(releaseInfo);
}
- echo("Generating site");
-
- // always write docs into the next major directory (so 2.0.0-alpha.0 writes to 2.0.0)
- target.gensite(nextMajor);
- generateBlogPost(releaseInfo);
- publishSite(`v${releaseInfo.version}`);
- echo("Site has been published");
-
- echo("Publishing to GitHub");
- ReleaseOps.publishReleaseToGitHub(releaseInfo);
+ commitSiteToGit(`v${releaseInfo.version}`);
}
+/**
+ * Publishes a generated release to npm and GitHub, and pushes changes to the adjacent `eslint.github.io` repo
+ * to remote repo.
+ * @returns {void}
+ */
+function publishRelease() {
+ ReleaseOps.publishRelease();
+ publishSite();
+}
/**
* Splits a command result to separate lines.
@@ -843,7 +849,7 @@ target.browserify = function() {
exec(`${getBinFile("browserify")} -x espree ${TEMP_DIR}linter.js -o ${BUILD_DIR}eslint.js -s eslint --global-transform [ babelify --presets [ es2015 ] ]`);
// 6. Browserify espree
- exec(`${getBinFile("browserify")} -r espree -o ${TEMP_DIR}espree.js`);
+ exec(`${getBinFile("browserify")} -r espree -o ${TEMP_DIR}espree.js --global-transform [ babelify --presets [ es2015 ] ]`);
// 7. Concatenate Babel polyfill, Espree, and ESLint files together
cat("./node_modules/babel-polyfill/dist/polyfill.js", `${TEMP_DIR}espree.js`, `${BUILD_DIR}eslint.js`).to(`${BUILD_DIR}eslint.js`);
@@ -1141,18 +1147,6 @@ target.perf = function() {
});
};
-target.release = function() {
- release();
-};
-
-target.ciRelease = function() {
- release(true);
-};
-
-target.publishsite = function() {
- publishSite();
-};
-
-target.prerelease = function(args) {
- prerelease(args[0]);
-};
+target.generateRelease = generateRelease;
+target.generatePrerelease = ([prereleaseType]) => generatePrerelease(prereleaseType);
+target.publishRelease = publishRelease;
diff --git a/bin/eslint.js b/bin/eslint.js
index adc07c2e75c3..e51121ec6afe 100755
--- a/bin/eslint.js
+++ b/bin/eslint.js
@@ -48,7 +48,6 @@ process.once("uncaughtException", err => {
console.error(`\nESLint: ${pkg.version}.\n${template(err.messageData || {})}`);
} else {
- console.error(err.message);
console.error(err.stack);
}
diff --git a/docs/developer-guide/README.md b/docs/developer-guide/README.md
index 2070b2da13ea..ea1943099778 100644
--- a/docs/developer-guide/README.md
+++ b/docs/developer-guide/README.md
@@ -34,7 +34,7 @@ You're finally ready to start working with rules. You may want to fix an existin
You've developed library-specific rules for ESLint and you want to share it with the community. You can publish an ESLint plugin on npm.
-## Section 6: [Working with Parsers](working-with-parsers.md)
+## Section 6: [Working with Custom Parsers](working-with-custom-parsers.md)
If you aren't going to use the default parser of ESLint, this section explains about using custom parsers.
diff --git a/docs/developer-guide/contributing/README.md b/docs/developer-guide/contributing/README.md
index a4e331ef9d63..a22ea5d90a26 100644
--- a/docs/developer-guide/contributing/README.md
+++ b/docs/developer-guide/contributing/README.md
@@ -28,6 +28,10 @@ Want to make a change to an existing rule? This section explains the process and
If you'd like to request a change other than a bug fix or new rule, this section explains that process.
+## Reporting a security vulnerability
+
+To report a security vulnerability in ESLint, please use our [HackerOne program](https://hackerone.com/eslint).
+
## [Working on Issues](working-on-issues.md)
Have some extra time and want to contribute? This section talks about the process of working on issues.
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index 2e3390b9365d..8dbccc7afb19 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -117,7 +117,8 @@ var messages = linter.verify("var foo;", {
// or using SourceCode
-var linter = require("eslint").linter,
+var Linter = require("eslint").Linter,
+ linter = new Linter(),
SourceCode = require("eslint").SourceCode;
var code = new SourceCode("var foo = bar;", ast);
@@ -290,7 +291,7 @@ linter.defineParser("my-custom-parser", {
const results = linter.verify("// some source text", { parser: "my-custom-parser" });
```
-### Linter#version
+### Linter#version/Linter.version
Each instance of `Linter` has a `version` property containing the semantic version number of ESLint that the `Linter` instance is from.
@@ -301,6 +302,14 @@ const linter = new Linter();
linter.version; // => '4.5.0'
```
+There is also a `Linter.version` property that you can read without instantiating `Linter`:
+
+```js
+const Linter = require("eslint").Linter;
+
+Linter.version; // => '4.5.0'
+```
+
## linter
The `eslint.linter` object (deprecated) is an instance of the `Linter` class as defined [above](#linter). `eslint.linter` exists for backwards compatibility, but we do not recommend using it because any mutations to it are shared among every module that uses `eslint`. Instead, please create your own instance of `eslint.Linter`.
diff --git a/docs/maintainer-guide/governance.md b/docs/maintainer-guide/governance.md
index 15e5a6340b25..81d5908eabc9 100644
--- a/docs/maintainer-guide/governance.md
+++ b/docs/maintainer-guide/governance.md
@@ -93,7 +93,7 @@ A Committer is invited to become a TSC member by existing TSC members. A nominat
1. Add the GitHub user to the "ESLint TSC" team
1. Set the GitHub user to be have the "Owner" role for the ESLint organization
-1. Send welcome email with link to maintainer guide
+1. Send a welcome email with a link to the [maintainer guide](./) and the [npm 2FA guide](./npm-2fa).
1. Add the TSC member to the README
1. Invite to the Gitter TSC chatroom
1. Make the TSC member an admin on the ESLint team mailing list
diff --git a/docs/maintainer-guide/npm-2fa.md b/docs/maintainer-guide/npm-2fa.md
new file mode 100644
index 000000000000..e9a517761fba
--- /dev/null
+++ b/docs/maintainer-guide/npm-2fa.md
@@ -0,0 +1,16 @@
+# npm two-factor authentication
+
+The `eslint` npm account has two-factor authentication (2FA) enabled. The 2FA secret is distributed using a team on [Keybase](https://keybase.io). Anyone doing a release of a package from the Jenkins server needs to have access to the 2FA secret.
+
+If you're on ESLint's TSC, you should perform the following steps to obtain the 2FA secret:
+
+1. Download the [Keybase app](https://keybase.io/download) on a smartphone.
+1. Open the app and create an account.
+1. From the app, link your Keybase username with your GitHub username. (At the time of writing, the UI for this is to tap the face icon in the bottom-left of the app, then the profile picture in the top-right, then tap "Prove your GitHub" and follow the instructions.)
+1. Mention your Keybase username in the team chatroom, and wait for someone to add you to the Keybase team.
+1. Download an authenticator app like [Google Authenticator](https://support.google.com/accounts/answer/1066447) or [Authy](https://authy.com/), if you don't have one installed already.
+1. In the Keybase app, navigate to the Keybase filesystem (at the time of writing, the UI for this is to tap the hamburger icon in the bottom-right, then tap "Files") and then navigate to `/team/eslint/auth`.
+ * If your authenticator app is downloaded on the same device as your Keybase app (this will usually be the case if you're using the Keybase mobile app), then open `npm_2fa_code.txt` and copy the contents to the clipboard. Open your authenticator app, and paste the contents as a new key (by selecting something like "Enter a provided key" or "Enter key manually").
+ * If your authenticator app is downloaded on a *different* device from your Keybase app (e.g. if you're using a Keybase desktop app), then open `npm_2fa_code.png` and scan it as a QR code from your authenticator app.
+
+You should now be able to generate 6-digit 2FA codes for the `eslint` npm account using your authenticator app.
diff --git a/docs/maintainer-guide/releases.md b/docs/maintainer-guide/releases.md
index e8faab5f63a7..9d00030f3dff 100644
--- a/docs/maintainer-guide/releases.md
+++ b/docs/maintainer-guide/releases.md
@@ -16,6 +16,8 @@ A two-person release team is assigned to each scheduled release. This two-person
The two-person team should seek input from the whole team on the Monday following a release to double-check if a patch release is necessary.
+At least one member of the release team needs to have access to [eslint's two-factor authentication for npm](./npm-2fa) in order to do a release.
+
## Release Communication
Each scheduled release should be associated with a release issue ([example](https://github.com/eslint/eslint/issues/8138)). The release issue is the source of information for the team about the status of a release. Be sure the release issue has the "release" label so that it's easy to find.
@@ -30,7 +32,9 @@ On the day of a scheduled release, the release team should follow these steps:
* Documentation changes.
* Small bug fixes written by a team member.
1. Log into Jenkins and schedule a build for the "ESLint Release" job.
-1. Wait for the "ESLint Release" job to complete.
+1. Watch the console output of the build on Jenkins. At some point, the build will pause and a link will be produced with an input field for a six-digit 2FA code.
+1. Enter the current six-digit 2FA code from your authenticator app. (Also see: [npm-2fa](./npm-2fa))
+1. Continue the build and wait for it to finish.
1. Update the release blog post with a "Highlights" section, including new rules and anything else that's important.
1. Make a release announcement in the public chatroom.
1. Make a release announcement on Twitter.
diff --git a/docs/rules/camelcase.md b/docs/rules/camelcase.md
index cce3fd92470f..1765f1852c47 100644
--- a/docs/rules/camelcase.md
+++ b/docs/rules/camelcase.md
@@ -14,6 +14,7 @@ This rule has an object option:
* `"properties": "never"` does not check property names
* `"ignoreDestructuring": false` (default) enforces camelcase style for destructured identifiers
* `"ignoreDestructuring": true` does not check destructured identifiers
+* `allow` (`string[]`) list of properties to accept. Accept regex.
### properties: "always"
@@ -151,6 +152,30 @@ var { category_id = 1 } = query;
var { category_id: category_id } = query;
```
+## allow
+
+Examples of **correct** code for this rule with the `allow` option:
+
+```js
+/*eslint camelcase: ["error", {allow: ["UNSAFE_componentWillMount"]}]*/
+
+function UNSAFE_componentWillMount() {
+ // ...
+}
+```
+
+```js
+/*eslint camelcase: ["error", {allow: ["^UNSAFE_"]}]*/
+
+function UNSAFE_componentWillMount() {
+ // ...
+}
+
+function UNSAFE_componentWillMount() {
+ // ...
+}
+```
+
## When Not To Use It
If you have established coding standards using a different naming convention (separating words with underscores), turn this rule off.
diff --git a/docs/rules/complexity.md b/docs/rules/complexity.md
index e70a8989ac1d..3a665491c39f 100644
--- a/docs/rules/complexity.md
+++ b/docs/rules/complexity.md
@@ -71,7 +71,7 @@ If you can't determine an appropriate complexity limit for your code, then it's
## Further Reading
* [Cyclomatic Complexity](https://en.wikipedia.org/wiki/Cyclomatic_complexity)
-* [Complexity Analysis of JavaScript Code](http://ariya.ofilabs.com/2012/12/complexity-analysis-of-javascript-code.html)
+* [Complexity Analysis of JavaScript Code](https://ariya.io/2012/12/complexity-analysis-of-javascript-code)
* [More about Complexity in JavaScript](https://craftsmanshipforsoftware.com/2015/05/25/complexity-for-javascript/)
* [About Complexity](https://web.archive.org/web/20160808115119/http://jscomplexity.org/complexity)
* [Discussion about Complexity in ESLint and more links](https://github.com/eslint/eslint/issues/4808#issuecomment-167795140)
diff --git a/docs/rules/no-tabs.md b/docs/rules/no-tabs.md
index 691b2f2af2a4..4e02d9a04c63 100644
--- a/docs/rules/no-tabs.md
+++ b/docs/rules/no-tabs.md
@@ -9,14 +9,14 @@ This rule looks for tabs anywhere inside a file: code, comments or anything else
Examples of **incorrect** code for this rule:
```js
-var a /t= 2;
+var a \t= 2;
/**
-* /t/t it's a test function
+* \t\t it's a test function
*/
function test(){}
-var x = 1; // /t test
+var x = 1; // \t test
```
Examples of **correct** code for this rule:
@@ -32,9 +32,29 @@ function test(){}
var x = 1; // test
```
+### Options
+
+This rule has an optional object option with the following properties:
+
+* `allowIndentationTabs` (default: false): If this is set to true, then the rule will not report tabs used for indentation.
+
+#### allowIndentationTabs
+
+Examples of **correct** code for this rule with the `allowIndentationTabs: true` option:
+
+```js
+/* eslint no-tabs: ["error", { allowIndentationTabs: true }] */
+
+function test() {
+\tdoSomething();
+}
+
+\t// comment with leading indentation tab
+```
+
## When Not To Use It
-If you have established a standard where having tabs is fine.
+If you have established a standard where having tabs is fine, then you can disable this rule.
## Compatibility
diff --git a/docs/rules/no-unneeded-ternary.md b/docs/rules/no-unneeded-ternary.md
index b2a6b0281ddd..b4caddf58555 100644
--- a/docs/rules/no-unneeded-ternary.md
+++ b/docs/rules/no-unneeded-ternary.md
@@ -23,10 +23,10 @@ Here is an example:
```js
// Bad
-var foo = bar ? bar : 1;
+foo(bar ? bar : 1);
// Good
-var foo = bar || 1;
+foo(bar || 1);
```
## Rule Details
@@ -41,6 +41,8 @@ Examples of **incorrect** code for this rule:
var a = x === 2 ? true : false;
var a = x ? true : false;
+
+var a = f(x ? x : 1);
```
Examples of **correct** code for this rule:
@@ -56,7 +58,7 @@ var a = x ? "Yes" : "No";
var a = x ? y : x;
-var a = x ? x : 1;
+var a = x ? x : 1; // Note that this is only allowed as it on the right hand side of an assignment; this type of ternary is disallowed everywhere else. See defaultAssignment option below for more details.
```
## Options
@@ -68,6 +70,8 @@ This rule has an object option:
### defaultAssignment
+The defaultAssignment option allows expressions of the form `x ? x : expr` (where `x` is any identifier and `expr` is any expression) as the right hand side of assignments (but nowhere else).
+
Examples of additional **incorrect** code for this rule with the `{ "defaultAssignment": false }` option:
```js
diff --git a/docs/rules/no-unsafe-finally.md b/docs/rules/no-unsafe-finally.md
index 1783e501fc80..fb3bb1ea5beb 100644
--- a/docs/rules/no-unsafe-finally.md
+++ b/docs/rules/no-unsafe-finally.md
@@ -49,7 +49,7 @@ JavaScript suspends the control flow statements of `try` and `catch` blocks unti
// We expect this function to return 0 from try block.
(() => {
label: try {
- return 0; // 1 is returned but suspended until finally block ends
+ return 0; // 0 is returned but suspended until finally block ends
} finally {
break label; // It breaks out the try-finally block, before 0 is returned.
}
diff --git a/docs/rules/padding-line-between-statements.md b/docs/rules/padding-line-between-statements.md
index c56259734535..390dada294ef 100644
--- a/docs/rules/padding-line-between-statements.md
+++ b/docs/rules/padding-line-between-statements.md
@@ -20,9 +20,9 @@ function foo() {
## Rule Details
-This rule does nothing if no configuration.
+This rule does nothing if no configurations are provided.
-A configuration is an object which has 3 properties; `blankLine`, `prev` and `next`. For example, `{ blankLine: "always", prev: "var", next: "return" }` means "it requires one or more blank lines between a variable declaration and a `return` statement."
+A configuration is an object which has 3 properties; `blankLine`, `prev` and `next`. For example, `{ blankLine: "always", prev: "var", next: "return" }` means "one or more blank lines are required between a variable declaration and a `return` statement."
You can supply any number of configurations. If a statement pair matches multiple configurations, the last matched configuration will be used.
```json
@@ -46,11 +46,11 @@ You can supply any number of configurations. If a statement pair matches multipl
- `STATEMENT_TYPE` is one of the following, or an array of the following.
- `"*"` is wildcard. This matches any statements.
- `"block"` is lonely blocks.
- - `"block-like"` is block like statements. This matches statements that the last token is the closing brace of blocks; e.g. `{ }`, `if (a) { }`, and `while (a) { }`.
+ - `"block-like"` is block like statements. This matches statements that the last token is the closing brace of blocks; e.g. `{ }`, `if (a) { }`, and `while (a) { }`. Also matches immediately invoked function expression statements.
- `"break"` is `break` statements.
- `"case"` is `case` labels.
- - `"cjs-export"` is `export` statements of CommonJS; e.g. `module.exports = 0`, `module.exports.foo = 1`, and `exports.foo = 2`. This is the special cases of assignment.
- - `"cjs-import"` is `import` statements of CommonJS; e.g. `const foo = require("foo")`. This is the special cases of variable declarations.
+ - `"cjs-export"` is `export` statements of CommonJS; e.g. `module.exports = 0`, `module.exports.foo = 1`, and `exports.foo = 2`. This is a special case of assignment.
+ - `"cjs-import"` is `import` statements of CommonJS; e.g. `const foo = require("foo")`. This is a special case of variable declarations.
- `"class"` is `class` declarations.
- `"const"` is `const` variable declarations.
- `"continue"` is `continue` statements.
@@ -64,6 +64,7 @@ You can supply any number of configurations. If a statement pair matches multipl
- `"for"` is `for` loop families. This matches all statements that the first token is `for` keyword.
- `"function"` is function declarations.
- `"if"` is `if` statements.
+ - `"iife"` is immediately invoked function expression statements. This matches calls on a function expression, optionally prefixed with a unary operator.
- `"import"` is `import` declarations.
- `"let"` is `let` variable declarations.
- `"multiline-block-like"` is block like statements. This is the same as `block-like` type, but only if the block is multiline.
diff --git a/docs/rules/prefer-const.md b/docs/rules/prefer-const.md
index cf86f5b7be0a..3e5a5d0c3feb 100644
--- a/docs/rules/prefer-const.md
+++ b/docs/rules/prefer-const.md
@@ -79,6 +79,15 @@ for (let i = 0, end = 10; i < end; ++i) {
console.log(a);
}
+// `predicate` is only assigned once but cannot be separately declared as `const`
+let predicate;
+[object.type, predicate] = foo();
+
+// `a` is only assigned once but cannot be separately declared as `const`
+let a;
+const b = {};
+({ a, c: b.c } = func());
+
// suggest to use `no-var` rule.
var b = 3;
console.log(b);
diff --git a/docs/rules/quotes.md b/docs/rules/quotes.md
index 02c748c4da8c..25a09fd0dbf5 100644
--- a/docs/rules/quotes.md
+++ b/docs/rules/quotes.md
@@ -147,6 +147,8 @@ var single = 'single';
var single = `single`;
```
+`{ "allowTemplateLiterals": false }` will not disallow the usage of all template literals. If you want to forbid any instance of template literals, use [no-restricted-syntax](https://eslint.org/docs/rules/no-restricted-syntax) and target the `TemplateLiteral` selector.
+
## When Not To Use It
If you do not need consistency in your string styles, you can safely disable this rule.
diff --git a/docs/rules/space-infix-ops.md b/docs/rules/space-infix-ops.md
index f0381877deb2..406e2030cf41 100644
--- a/docs/rules/space-infix-ops.md
+++ b/docs/rules/space-infix-ops.md
@@ -73,3 +73,7 @@ var {a = 0} = bar;
function foo(a = 0) { }
```
+
+## When Not To Use It
+
+You can turn this rule off if you are not concerned with the consistency of spacing around infix operators.
diff --git a/docs/user-guide/command-line-interface.md b/docs/user-guide/command-line-interface.md
index 7de5a50a1e28..62ecd3c20215 100644
--- a/docs/user-guide/command-line-interface.md
+++ b/docs/user-guide/command-line-interface.md
@@ -401,7 +401,7 @@ Store the info about processed files in order to only operate on the changed one
**Note:** If you run ESLint with `--cache` and then run ESLint without `--cache`, the `.eslintcache` file will be deleted. This is necessary because the results of the lint might change and make `.eslintcache` invalid. If you want to control when the cache file is deleted, then use `--cache-location` to specify an alternate location for the cache file.
-**Note:**: As of now, only the results for successfully linted files are stored in the cache. Files which fail linting are not stored in the cache, so they will be linted every time.
+**Note:**: Autofixed files are not placed in the cache. Subsequent linting that does not trigger an autofix will place it in the cache.
#### `--cache-file`
diff --git a/docs/user-guide/configuring.md b/docs/user-guide/configuring.md
index 0db1e4dcf403..3bb64faf871b 100644
--- a/docs/user-guide/configuring.md
+++ b/docs/user-guide/configuring.md
@@ -763,7 +763,7 @@ module.exports = {
## Configuration Based on Glob Patterns
-Sometimes a more fine-controlled configuration is necessary, for example if the configuration for files within the same directory has to be different. Therefore you can provide configurations under the `overrides` key that will only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`).
+v4.1.0+. Sometimes a more fine-controlled configuration is necessary, for example if the configuration for files within the same directory has to be different. Therefore you can provide configurations under the `overrides` key that will only apply to files that match specific glob patterns, using the same format you would pass on the command line (e.g., `app/**/*.test.js`).
### How it works
diff --git a/lib/linter.js b/lib/linter.js
index 8f7bdf9e4c79..b47e6eb1fcfb 100644
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -888,6 +888,15 @@ module.exports = class Linter {
this.environments = new Environments();
}
+ /**
+ * Getter for package version.
+ * @static
+ * @returns {string} The version from package.json.
+ */
+ static get version() {
+ return pkg.version;
+ }
+
/**
* Configuration object for the `verify` API. A JS representation of the eslintrc files.
* @typedef {Object} ESLintConfig
diff --git a/lib/rules/camelcase.js b/lib/rules/camelcase.js
index 3005963ab04c..41040450f942 100644
--- a/lib/rules/camelcase.js
+++ b/lib/rules/camelcase.js
@@ -27,6 +27,16 @@ module.exports = {
},
properties: {
enum: ["always", "never"]
+ },
+ allow: {
+ type: "array",
+ items: [
+ {
+ type: "string"
+ }
+ ],
+ minItems: 0,
+ uniqueItems: true
}
},
additionalProperties: false
@@ -40,6 +50,15 @@ module.exports = {
create(context) {
+ const options = context.options[0] || {};
+ let properties = options.properties || "";
+ const ignoreDestructuring = options.ignoreDestructuring || false;
+ const allow = options.allow || [];
+
+ if (properties !== "always" && properties !== "never") {
+ properties = "always";
+ }
+
//--------------------------------------------------------------------------
// Helpers
//--------------------------------------------------------------------------
@@ -60,6 +79,18 @@ module.exports = {
return name.indexOf("_") > -1 && name !== name.toUpperCase();
}
+ /**
+ * Checks if a string match the ignore list
+ * @param {string} name The string to check.
+ * @returns {boolean} if the string is ignored
+ * @private
+ */
+ function isAllowed(name) {
+ return allow.findIndex(
+ entry => name === entry || name.match(new RegExp(entry))
+ ) !== -1;
+ }
+
/**
* Checks if a parent of a node is an ObjectPattern.
* @param {ASTNode} node The node to check.
@@ -93,14 +124,6 @@ module.exports = {
}
}
- const options = context.options[0] || {};
- let properties = options.properties || "";
- const ignoreDestructuring = options.ignoreDestructuring || false;
-
- if (properties !== "always" && properties !== "never") {
- properties = "always";
- }
-
return {
Identifier(node) {
@@ -112,6 +135,11 @@ module.exports = {
const name = node.name.replace(/^_+|_+$/g, ""),
effectiveParent = (node.parent.type === "MemberExpression") ? node.parent.parent : node.parent;
+ // First, we ignore the node if it match the ignore list
+ if (isAllowed(name)) {
+ return;
+ }
+
// MemberExpressions get special rules
if (node.parent.type === "MemberExpression") {
diff --git a/lib/rules/no-extra-bind.js b/lib/rules/no-extra-bind.js
index b8376adfc117..6d6cad13e97d 100644
--- a/lib/rules/no-extra-bind.js
+++ b/lib/rules/no-extra-bind.js
@@ -10,6 +10,12 @@
const astUtils = require("../util/ast-utils");
+//------------------------------------------------------------------------------
+// Helpers
+//------------------------------------------------------------------------------
+
+const SIDE_EFFECT_FREE_NODE_TYPES = new Set(["Literal", "Identifier", "ThisExpression", "FunctionExpression"]);
+
//------------------------------------------------------------------------------
// Rule Definition
//------------------------------------------------------------------------------
@@ -35,6 +41,18 @@ module.exports = {
create(context) {
let scopeInfo = null;
+ /**
+ * Checks if a node is free of side effects.
+ *
+ * This check is stricter than it needs to be, in order to keep the implementation simple.
+ *
+ * @param {ASTNode} node A node to check.
+ * @returns {boolean} True if the node is known to be side-effect free, false otherwise.
+ */
+ function isSideEffectFree(node) {
+ return SIDE_EFFECT_FREE_NODE_TYPES.has(node.type);
+ }
+
/**
* Reports a given function node.
*
@@ -48,6 +66,10 @@ module.exports = {
messageId: "unexpected",
loc: node.parent.property.loc.start,
fix(fixer) {
+ if (node.parent.parent.arguments.length && !isSideEffectFree(node.parent.parent.arguments[0])) {
+ return null;
+ }
+
const firstTokenToRemove = context.getSourceCode()
.getFirstTokenBetween(node.parent.object, node.parent.property, astUtils.isNotClosingParenToken);
diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js
index 729f829a1f71..de6934c635ac 100644
--- a/lib/rules/no-irregular-whitespace.js
+++ b/lib/rules/no-irregular-whitespace.js
@@ -81,9 +81,7 @@ module.exports = {
const locStart = node.loc.start;
const locEnd = node.loc.end;
- errors = errors.filter(error => {
- const errorLoc = error[1];
-
+ errors = errors.filter(({ loc: errorLoc }) => {
if (errorLoc.line >= locStart.line && errorLoc.line <= locEnd.line) {
if (errorLoc.column >= locStart.column && (errorLoc.column <= locEnd.column || errorLoc.line < locEnd.line)) {
return false;
@@ -157,7 +155,7 @@ module.exports = {
column: match.index
};
- errors.push([node, location, "Irregular whitespace not allowed."]);
+ errors.push({ node, message: "Irregular whitespace not allowed.", loc: location });
}
});
}
@@ -182,7 +180,7 @@ module.exports = {
column: sourceLines[lineIndex].length
};
- errors.push([node, location, "Irregular whitespace not allowed."]);
+ errors.push({ node, message: "Irregular whitespace not allowed.", loc: location });
lastLineIndex = lineIndex;
}
}
@@ -224,9 +222,7 @@ module.exports = {
}
// If we have any errors remaining report on them
- errors.forEach(error => {
- context.report(...error);
- });
+ errors.forEach(error => context.report(error));
};
} else {
nodes.Program = noop;
diff --git a/lib/rules/no-tabs.js b/lib/rules/no-tabs.js
index 08a8fa5b758b..c22a94da38be 100644
--- a/lib/rules/no-tabs.js
+++ b/lib/rules/no-tabs.js
@@ -8,7 +8,9 @@
//------------------------------------------------------------------------------
// Helpers
//------------------------------------------------------------------------------
-const regex = /\t/;
+
+const tabRegex = /\t+/g;
+const anyNonWhitespaceRegex = /\S/;
//------------------------------------------------------------------------------
// Public Interface
@@ -22,21 +24,36 @@ module.exports = {
recommended: false,
url: "https://eslint.org/docs/rules/no-tabs"
},
- schema: []
+ schema: [{
+ type: "object",
+ properties: {
+ allowIndentationTabs: {
+ type: "boolean"
+ }
+ },
+ additionalProperties: false
+ }]
},
create(context) {
+ const sourceCode = context.getSourceCode();
+ const allowIndentationTabs = context.options && context.options[0] && context.options[0].allowIndentationTabs;
+
return {
Program(node) {
- context.getSourceCode().getLines().forEach((line, index) => {
- const match = regex.exec(line);
+ sourceCode.getLines().forEach((line, index) => {
+ let match;
+
+ while ((match = tabRegex.exec(line)) !== null) {
+ if (allowIndentationTabs && !anyNonWhitespaceRegex.test(line.slice(0, match.index))) {
+ continue;
+ }
- if (match) {
context.report({
node,
loc: {
line: index + 1,
- column: match.index + 1
+ column: match.index
},
message: "Unexpected tab character."
});
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index fff3e33d9235..6f36813aca57 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -469,7 +469,7 @@ module.exports = {
const posteriorParams = params.slice(params.indexOf(variable) + 1);
// If any used parameters occur after this parameter, do not report.
- return !posteriorParams.some(v => v.references.length > 0);
+ return !posteriorParams.some(v => v.references.length > 0 || v.eslintUsed);
}
/**
diff --git a/lib/rules/one-var.js b/lib/rules/one-var.js
index 13ab72b04ae5..3efd0f27f7f3 100644
--- a/lib/rules/one-var.js
+++ b/lib/rules/one-var.js
@@ -314,6 +314,11 @@ module.exports = {
function splitDeclarations(declaration) {
return fixer => declaration.declarations.map(declarator => {
const tokenAfterDeclarator = sourceCode.getTokenAfter(declarator);
+
+ if (tokenAfterDeclarator === null) {
+ return null;
+ }
+
const afterComma = sourceCode.getTokenAfter(tokenAfterDeclarator, { includeComments: true });
if (tokenAfterDeclarator.value !== ",") {
@@ -384,8 +389,13 @@ module.exports = {
if (nodeIndex > 0) {
const previousNode = parent.body[nodeIndex - 1];
const isPreviousNodeDeclaration = previousNode.type === "VariableDeclaration";
+ const declarationsWithPrevious = declarations.concat(previousNode.declarations || []);
- if (isPreviousNodeDeclaration && previousNode.kind === type) {
+ if (
+ isPreviousNodeDeclaration &&
+ previousNode.kind === type &&
+ !(declarationsWithPrevious.some(isRequire) && !declarationsWithPrevious.every(isRequire))
+ ) {
const previousDeclCounts = countDeclarations(previousNode.declarations);
if (options[type].initialized === MODE_CONSECUTIVE && options[type].uninitialized === MODE_CONSECUTIVE) {
diff --git a/lib/rules/padding-line-between-statements.js b/lib/rules/padding-line-between-statements.js
index f571e1e2e335..4af85fef015a 100644
--- a/lib/rules/padding-line-between-statements.js
+++ b/lib/rules/padding-line-between-statements.js
@@ -353,6 +353,9 @@ const StatementTypes = {
node.type === "ExpressionStatement" &&
!isDirectivePrologue(node, sourceCode)
},
+ iife: {
+ test: isIIFEStatement
+ },
"multiline-block-like": {
test: (node, sourceCode) =>
node.loc.start.line !== node.loc.end.line &&
diff --git a/lib/rules/prefer-const.js b/lib/rules/prefer-const.js
index 774fcf064376..8b3bc5e0e436 100644
--- a/lib/rules/prefer-const.js
+++ b/lib/rules/prefer-const.js
@@ -57,6 +57,7 @@ function canBecomeVariableDeclaration(identifier) {
* @returns {boolean} Indicates if the variable is from outer scope or function parameters.
*/
function isOuterVariableInDestructing(name, initScope) {
+
if (initScope.through.find(ref => ref.resolved && ref.resolved.name === name)) {
return true;
}
@@ -96,6 +97,54 @@ function getDestructuringHost(reference) {
return node;
}
+/**
+ * Determines if a destructuring assignment node contains
+ * any MemberExpression nodes. This is used to determine if a
+ * variable that is only written once using destructuring can be
+ * safely converted into a const declaration.
+ * @param {ASTNode} node The ObjectPattern or ArrayPattern node to check.
+ * @returns {boolean} True if the destructuring pattern contains
+ * a MemberExpression, false if not.
+ */
+function hasMemberExpressionAssignment(node) {
+ switch (node.type) {
+ case "ObjectPattern":
+ return node.properties.some(prop => {
+ if (prop) {
+
+ /*
+ * Spread elements have an argument property while
+ * others have a value property. Because different
+ * parsers use different node types for spread elements,
+ * we just check if there is an argument property.
+ */
+ return hasMemberExpressionAssignment(prop.argument || prop.value);
+ }
+
+ return false;
+ });
+
+ case "ArrayPattern":
+ return node.elements.some(element => {
+ if (element) {
+ return hasMemberExpressionAssignment(element);
+ }
+
+ return false;
+ });
+
+ case "AssignmentPattern":
+ return hasMemberExpressionAssignment(node.left);
+
+ case "MemberExpression":
+ return true;
+
+ // no default
+ }
+
+ return false;
+}
+
/**
* Gets an identifier node of a given variable.
*
@@ -148,7 +197,8 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
if (destructuringHost !== null && destructuringHost.left !== void 0) {
const leftNode = destructuringHost.left;
- let hasOuterVariables = false;
+ let hasOuterVariables = false,
+ hasNonIdentifiers = false;
if (leftNode.type === "ObjectPattern") {
const properties = leftNode.properties;
@@ -157,16 +207,23 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
.filter(prop => prop.value)
.map(prop => prop.value.name)
.some(name => isOuterVariableInDestructing(name, variable.scope));
+
+ hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
+
} else if (leftNode.type === "ArrayPattern") {
const elements = leftNode.elements;
hasOuterVariables = elements
.map(element => element && element.name)
.some(name => isOuterVariableInDestructing(name, variable.scope));
+
+ hasNonIdentifiers = hasMemberExpressionAssignment(leftNode);
}
- if (hasOuterVariables) {
+
+ if (hasOuterVariables || hasNonIdentifiers) {
return null;
}
+
}
writer = reference;
@@ -192,9 +249,11 @@ function getIdentifierIfShouldBeConst(variable, ignoreReadBeforeAssign) {
if (!shouldBeConst) {
return null;
}
+
if (isReadBeforeInit) {
return variable.defs[0].name;
}
+
return writer.identifier;
}
@@ -295,7 +354,7 @@ module.exports = {
create(context) {
const options = context.options[0] || {};
const sourceCode = context.getSourceCode();
- const checkingMixedDestructuring = options.destructuring !== "all";
+ const shouldMatchAnyDestructuredVariable = options.destructuring !== "all";
const ignoreReadBeforeAssign = options.ignoreReadBeforeAssign === true;
const variables = [];
@@ -316,7 +375,7 @@ module.exports = {
function checkGroup(nodes) {
const nodesToReport = nodes.filter(Boolean);
- if (nodes.length && (checkingMixedDestructuring || nodesToReport.length === nodes.length)) {
+ if (nodes.length && (shouldMatchAnyDestructuredVariable || nodesToReport.length === nodes.length)) {
const varDeclParent = findUp(nodes[0], "VariableDeclaration", parentNode => parentNode.type.endsWith("Statement"));
const shouldFix = varDeclParent &&
diff --git a/lib/rules/require-atomic-updates.js b/lib/rules/require-atomic-updates.js
index 5d7d7151455e..c6cf0d747740 100644
--- a/lib/rules/require-atomic-updates.js
+++ b/lib/rules/require-atomic-updates.js
@@ -119,6 +119,66 @@ module.exports = {
});
}
+ const alreadyReportedAssignments = new WeakSet();
+
+ class AssignmentTrackerState {
+ constructor({ openAssignmentsWithoutReads = new Set(), openAssignmentsWithReads = new Set() } = {}) {
+ this.openAssignmentsWithoutReads = openAssignmentsWithoutReads;
+ this.openAssignmentsWithReads = openAssignmentsWithReads;
+ }
+
+ copy() {
+ return new AssignmentTrackerState({
+ openAssignmentsWithoutReads: new Set(this.openAssignmentsWithoutReads),
+ openAssignmentsWithReads: new Set(this.openAssignmentsWithReads)
+ });
+ }
+
+ merge(other) {
+ const initialAssignmentsWithoutReadsCount = this.openAssignmentsWithoutReads.size;
+ const initialAssignmentsWithReadsCount = this.openAssignmentsWithReads.size;
+
+ other.openAssignmentsWithoutReads.forEach(assignment => this.openAssignmentsWithoutReads.add(assignment));
+ other.openAssignmentsWithReads.forEach(assignment => this.openAssignmentsWithReads.add(assignment));
+
+ return this.openAssignmentsWithoutReads.size > initialAssignmentsWithoutReadsCount ||
+ this.openAssignmentsWithReads.size > initialAssignmentsWithReadsCount;
+ }
+
+ enterAssignment(assignmentExpression) {
+ (assignmentExpression.operator === "=" ? this.openAssignmentsWithoutReads : this.openAssignmentsWithReads).add(assignmentExpression);
+ }
+
+ exitAssignment(assignmentExpression) {
+ this.openAssignmentsWithoutReads.delete(assignmentExpression);
+ this.openAssignmentsWithReads.delete(assignmentExpression);
+ }
+
+ exitAwaitOrYield(node, surroundingFunction) {
+ return [...this.openAssignmentsWithReads]
+ .filter(assignment => !isLocalVariableWithoutEscape(assignment.left, surroundingFunction))
+ .forEach(assignment => {
+ if (!alreadyReportedAssignments.has(assignment)) {
+ reportAssignment(assignment);
+ alreadyReportedAssignments.add(assignment);
+ }
+ });
+ }
+
+ exitIdentifierOrMemberExpression(node) {
+ [...this.openAssignmentsWithoutReads]
+ .filter(assignment => (
+ assignment.left !== node &&
+ assignment.left.type === node.type &&
+ astUtils.equalTokens(assignment.left, node, sourceCode)
+ ))
+ .forEach(assignment => {
+ this.openAssignmentsWithoutReads.delete(assignment);
+ this.openAssignmentsWithReads.add(assignment);
+ });
+ }
+ }
+
/**
* If the control flow graph of a function enters an assignment expression, then does the
* both of the following steps in order (possibly with other steps in between) before exiting the
@@ -135,54 +195,51 @@ module.exports = {
codePathSegment,
surroundingFunction,
{
- seenSegments = new Set(),
- openAssignmentsWithoutReads = new Set(),
- openAssignmentsWithReads = new Set()
+ stateBySegmentStart = new WeakMap(),
+ stateBySegmentEnd = new WeakMap()
} = {}
) {
- if (seenSegments.has(codePathSegment)) {
-
- // An AssignmentExpression can't contain loops, so it's not necessary to reenter them with new state.
- return;
+ if (!stateBySegmentStart.has(codePathSegment)) {
+ stateBySegmentStart.set(codePathSegment, new AssignmentTrackerState());
}
+ const currentState = stateBySegmentStart.get(codePathSegment).copy();
+
expressionsByCodePathSegment.get(codePathSegment).forEach(({ entering, node }) => {
if (node.type === "AssignmentExpression") {
if (entering) {
- (node.operator === "=" ? openAssignmentsWithoutReads : openAssignmentsWithReads).add(node);
+ currentState.enterAssignment(node);
} else {
- openAssignmentsWithoutReads.delete(node);
- openAssignmentsWithReads.delete(node);
+ currentState.exitAssignment(node);
}
} else if (!entering && (node.type === "AwaitExpression" || node.type === "YieldExpression")) {
- [...openAssignmentsWithReads]
- .filter(assignment => !isLocalVariableWithoutEscape(assignment.left, surroundingFunction))
- .forEach(reportAssignment);
-
- openAssignmentsWithReads.clear();
+ currentState.exitAwaitOrYield(node, surroundingFunction);
} else if (!entering && (node.type === "Identifier" || node.type === "MemberExpression")) {
- [...openAssignmentsWithoutReads]
- .filter(assignment => (
- assignment.left !== node &&
- assignment.left.type === node.type &&
- astUtils.equalTokens(assignment.left, node, sourceCode)
- ))
- .forEach(assignment => {
- openAssignmentsWithoutReads.delete(assignment);
- openAssignmentsWithReads.add(assignment);
- });
+ currentState.exitIdentifierOrMemberExpression(node);
}
});
+ stateBySegmentEnd.set(codePathSegment, currentState);
+
codePathSegment.nextSegments.forEach(nextSegment => {
+ if (stateBySegmentStart.has(nextSegment)) {
+ if (!stateBySegmentStart.get(nextSegment).merge(currentState)) {
+
+ /*
+ * This segment has already been processed with the given set of inputs;
+ * no need to do it again. After no new state is available to process
+ * for any control flow segment in the graph, the analysis reaches a fixpoint and
+ * traversal stops.
+ */
+ return;
+ }
+ } else {
+ stateBySegmentStart.set(nextSegment, currentState.copy());
+ }
findOutdatedReads(
nextSegment,
surroundingFunction,
- {
- seenSegments: new Set(seenSegments).add(codePathSegment),
- openAssignmentsWithoutReads: new Set(openAssignmentsWithoutReads),
- openAssignmentsWithReads: new Set(openAssignmentsWithReads)
- }
+ { stateBySegmentStart, stateBySegmentEnd }
);
});
}
diff --git a/lib/rules/space-infix-ops.js b/lib/rules/space-infix-ops.js
index 49b64658d190..17b49cc1184c 100644
--- a/lib/rules/space-infix-ops.js
+++ b/lib/rules/space-infix-ops.js
@@ -34,37 +34,25 @@ module.exports = {
create(context) {
const int32Hint = context.options[0] ? context.options[0].int32Hint === true : false;
-
- const OPERATORS = [
- "*", "/", "%", "+", "-", "<<", ">>", ">>>", "<", "<=", ">", ">=", "in",
- "instanceof", "==", "!=", "===", "!==", "&", "^", "|", "&&", "||", "=",
- "+=", "-=", "*=", "/=", "%=", "<<=", ">>=", ">>>=", "&=", "^=", "|=",
- "?", ":", ",", "**"
- ];
-
const sourceCode = context.getSourceCode();
/**
* Returns the first token which violates the rule
* @param {ASTNode} left - The left node of the main node
* @param {ASTNode} right - The right node of the main node
+ * @param {string} op - The operator of the main node
* @returns {Object} The violator token or null
* @private
*/
- function getFirstNonSpacedToken(left, right) {
- const tokens = sourceCode.getTokensBetween(left, right, 1);
-
- for (let i = 1, l = tokens.length - 1; i < l; ++i) {
- const op = tokens[i];
-
- if (
- (op.type === "Punctuator" || op.type === "Keyword") &&
- OPERATORS.indexOf(op.value) >= 0 &&
- (tokens[i - 1].range[1] >= op.range[0] || op.range[1] >= tokens[i + 1].range[0])
- ) {
- return op;
- }
+ function getFirstNonSpacedToken(left, right, op) {
+ const operator = sourceCode.getFirstTokenBetween(left, right, token => token.value === op);
+ const prev = sourceCode.getTokenBefore(operator);
+ const next = sourceCode.getTokenAfter(operator);
+
+ if (!sourceCode.isSpaceBetweenTokens(prev, operator) || !sourceCode.isSpaceBetweenTokens(operator, next)) {
+ return operator;
}
+
return null;
}
@@ -110,7 +98,10 @@ module.exports = {
const leftNode = (node.left.typeAnnotation) ? node.left.typeAnnotation : node.left;
const rightNode = node.right;
- const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode);
+ // search for = in AssignmentPattern nodes
+ const operator = node.operator || "=";
+
+ const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, operator);
if (nonSpacedNode) {
if (!(int32Hint && sourceCode.getText(node).endsWith("|0"))) {
@@ -126,8 +117,8 @@ module.exports = {
* @private
*/
function checkConditional(node) {
- const nonSpacedConsequesntNode = getFirstNonSpacedToken(node.test, node.consequent);
- const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate);
+ const nonSpacedConsequesntNode = getFirstNonSpacedToken(node.test, node.consequent, "?");
+ const nonSpacedAlternateNode = getFirstNonSpacedToken(node.consequent, node.alternate, ":");
if (nonSpacedConsequesntNode) {
report(node, nonSpacedConsequesntNode);
@@ -147,7 +138,7 @@ module.exports = {
const rightNode = node.init;
if (rightNode) {
- const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode);
+ const nonSpacedNode = getFirstNonSpacedToken(leftNode, rightNode, "=");
if (nonSpacedNode) {
report(node, nonSpacedNode);
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index 9fbca945b62e..6d1bba989fde 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -397,7 +397,7 @@ class RuleTester {
*/
function assertASTDidntChange(beforeAST, afterAST) {
if (!lodash.isEqual(beforeAST, afterAST)) {
- assert.fail(null, null, "Rule should not modify AST.");
+ assert.fail("Rule should not modify AST.");
}
}
@@ -551,7 +551,7 @@ class RuleTester {
} else {
// Message was an unexpected type
- assert.fail(message, null, "Error should be a string, object, or RegExp.");
+ assert.fail(`Error should be a string, object, or RegExp, but found (${util.inspect(message)})`);
}
}
}
diff --git a/lib/util/source-code-fixer.js b/lib/util/source-code-fixer.js
index b3354d4d7625..53dc1dc6be73 100644
--- a/lib/util/source-code-fixer.js
+++ b/lib/util/source-code-fixer.js
@@ -8,7 +8,7 @@
// Requirements
//------------------------------------------------------------------------------
-const debug = require("debug")("eslint:text-fixer");
+const debug = require("debug")("eslint:source-code-fixer");
//------------------------------------------------------------------------------
// Helpers
diff --git a/package.json b/package.json
index 1a37b3c21b74..c2f7626dca64 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
{
"name": "eslint",
- "version": "5.6.0",
+ "version": "5.8.0",
"author": "Nicholas C. Zakas ",
"description": "An AST-based pattern checker for JavaScript.",
"bin": {
@@ -11,11 +11,11 @@
"test": "node Makefile.js test",
"lint": "node Makefile.js lint",
"fuzz": "node Makefile.js fuzz",
- "release": "node Makefile.js release",
- "ci-release": "node Makefile.js ciRelease",
- "alpharelease": "node Makefile.js prerelease -- alpha",
- "betarelease": "node Makefile.js prerelease -- beta",
- "rcrelease": "node Makefile.js prerelease -- rc",
+ "generate-release": "node Makefile.js generateRelease",
+ "generate-alpharelease": "node Makefile.js generatePrerelease -- alpha",
+ "generate-betarelease": "node Makefile.js generatePrerelease -- beta",
+ "generate-rcrelease": "node Makefile.js generatePrerelease -- rc",
+ "publish-release": "node Makefile.js publishRelease",
"docs": "node Makefile.js docs",
"gensite": "node Makefile.js gensite",
"browserify": "node Makefile.js browserify",
@@ -39,7 +39,7 @@
"ajv": "^6.5.3",
"chalk": "^2.1.0",
"cross-spawn": "^6.0.5",
- "debug": "^3.1.0",
+ "debug": "^4.0.1",
"doctrine": "^2.1.0",
"eslint-scope": "^4.0.0",
"eslint-utils": "^1.3.1",
@@ -66,12 +66,12 @@
"path-is-inside": "^1.0.2",
"pluralize": "^7.0.0",
"progress": "^2.0.0",
- "regexpp": "^2.0.0",
+ "regexpp": "^2.0.1",
"require-uncached": "^1.0.3",
"semver": "^5.5.1",
"strip-ansi": "^4.0.0",
"strip-json-comments": "^2.0.1",
- "table": "^4.0.3",
+ "table": "^5.0.2",
"text-table": "^0.2.0"
},
"devDependencies": {
@@ -90,7 +90,7 @@
"eslint-plugin-eslint-plugin": "^1.2.0",
"eslint-plugin-node": "^7.0.1",
"eslint-plugin-rulesdir": "^0.1.0",
- "eslint-release": "^0.11.1",
+ "eslint-release": "^1.0.0",
"eslint-rule-composer": "^0.3.0",
"eslump": "^1.6.2",
"esprima": "^4.0.1",
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index 75e7c2c4b7c8..7749b12f1071 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -333,12 +333,28 @@ describe("bin/eslint.js", () => {
return Promise.all([exitCodeAssertion, outputAssertion]);
});
+ it("prints the error message exactly once to stderr in the event of a crash", () => {
+ const child = runESLint(["--rule=no-restricted-syntax:[error, 'Invalid Selector [[[']", "Makefile.js"]);
+ const exitCodeAssertion = assertExitCode(child, 2);
+ const outputAssertion = getOutput(child).then(output => {
+ const expectedSubstring = "Syntax error in selector";
+
+ assert.strictEqual(output.stdout, "");
+ assert.include(output.stderr, expectedSubstring);
+
+ // The message should appear exactly once in stderr
+ assert.strictEqual(output.stderr.indexOf(expectedSubstring), output.stderr.lastIndexOf(expectedSubstring));
+ });
+
+ return Promise.all([exitCodeAssertion, outputAssertion]);
+ });
+
it("prints the error message pointing to line of code", () => {
const invalidConfig = `${__dirname}/../fixtures/bin/.eslintrc.yml`;
const child = runESLint(["--no-ignore", invalidConfig]);
const exitCodeAssertion = assertExitCode(child, 2);
const outputAssertion = getOutput(child).then(output => {
- const expectedSubstring = "Error: bad indentation of a mapping entry at line";
+ const expectedSubstring = ": bad indentation of a mapping entry at line";
assert.strictEqual(output.stdout, "");
assert.include(output.stderr, expectedSubstring);
diff --git a/tests/fixtures/parsers/typescript-parsers/type-alias.js b/tests/fixtures/parsers/typescript-parsers/type-alias.js
new file mode 100644
index 000000000000..f1d68c486942
--- /dev/null
+++ b/tests/fixtures/parsers/typescript-parsers/type-alias.js
@@ -0,0 +1,155 @@
+"use strict";
+
+exports.parse = () => ({
+ type: "Program",
+ range: [0, 16],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 16 }
+ },
+ body: [
+ {
+ type: "VariableDeclaration",
+ range: [0, 16],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 16 }
+ },
+ kind: "type",
+ declarations: [
+ {
+ type: "VariableDeclarator",
+ id: {
+ type: "Identifier",
+ range: [
+ 5,
+ 8
+ ],
+ loc: {
+ start: { line: 1, column: 5 },
+ end: { line: 1, column: 8 }
+ },
+ name: "Foo"
+ },
+ init: {
+ type: "TSTypeReference",
+ range: [14, 15],
+ loc: {
+ start: { line: 1, column: 14 },
+ end: { line: 1, column: 15 }
+ },
+ typeName: {
+ type: "Identifier",
+ range: [14, 15],
+ loc: {
+ start: { line: 1, column: 14 },
+ end: { line: 1, column: 15 }
+ },
+ name: "T"
+ }
+ },
+ range: [5, 16],
+ loc: {
+ start: { line: 1, column: 5 },
+ end: { line: 1, column: 16 }
+ },
+ typeParameters: {
+ type: "TSTypeParameterDeclaration",
+ range: [8, 11],
+ loc: {
+ start: { line: 1, column: 8 },
+ end: { line: 1, column: 11 }
+ },
+ params: [
+ {
+ type: "TSTypeParameter",
+ range: [9, 10],
+ loc: {
+ start: { line: 1, column: 9 },
+ end: { line: 1, column: 10 }
+ },
+ name: "T"
+ }
+ ]
+ }
+ }
+ ]
+ }
+ ],
+ sourceType: "script",
+ tokens: [
+ {
+ type: "Identifier",
+ value: "type",
+ range: [0, 4],
+ loc: {
+ start: { line: 1, column: 0 },
+ end: { line: 1, column: 4 }
+ }
+ },
+ {
+ type: "Identifier",
+ value: "Foo",
+ range: [5, 8],
+ loc: {
+ start: { line: 1, column: 5 },
+ end: { line: 1, column: 8 }
+ }
+ },
+ {
+ type: "Punctuator",
+ value: "<",
+ range: [8, 9],
+ loc: {
+ start: { line: 1, column: 8 },
+ end: { line: 1, column: 9 }
+ }
+ },
+ {
+ type: "Identifier",
+ value: "T",
+ range: [9, 10],
+ loc: {
+ start: { line: 1, column: 9 },
+ end: { line: 1, column: 10 }
+ }
+ },
+ {
+ type: "Punctuator",
+ value: ">",
+ range: [10, 11],
+ loc: {
+ start: { line: 1, column: 10 },
+ end: { line: 1, column: 11 }
+ }
+ },
+ {
+ type: "Punctuator",
+ value: "=",
+ range: [12, 13],
+ loc: {
+ start: { line: 1, column: 12 },
+ end: { line: 1, column: 13 }
+ }
+ },
+ {
+ type: "Identifier",
+ value: "T",
+ range: [14, 15],
+ loc: {
+ start: { line: 1, column: 14 },
+ end: { line: 1, column: 15 }
+ }
+ },
+ {
+ type: "Punctuator",
+ value: ";",
+ range: [15, 16],
+ loc: {
+ start: { line: 1, column: 15 },
+ end: { line: 1, column: 16 }
+ }
+ }
+ ],
+ comments: []
+})
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index 456b1518364e..b4cb2e41d8d4 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -342,7 +342,7 @@ describe("Validator", () => {
it("should throw if override has an empty files array", () => {
const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", ruleMapper, linter.environments);
- assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have less than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n");
+ assert.throws(fn, "ESLint configuration in tests is invalid:\n\t- Property \"overrides[0].files\" is the wrong type (expected string but got `[]`).\n\t- \"overrides[0].files\" should NOT have fewer than 1 items. Value: [].\n\t- \"overrides[0].files\" should match exactly one schema in oneOf. Value: [].\n");
});
it("should throw if override has nested overrides", () => {
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index dd8e10b951e1..5b2f2c31a7c7 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -76,6 +76,14 @@ describe("Linter", () => {
sandbox.verifyAndRestore();
});
+ describe("Static Members", () => {
+ describe("version", () => {
+ it("should return same version as instance property", () => {
+ assert.strictEqual(Linter.version, linter.version);
+ });
+ });
+ });
+
describe("when using events", () => {
const code = TEST_CODE;
diff --git a/tests/lib/rules/camelcase.js b/tests/lib/rules/camelcase.js
index 481842f97392..3d2b7516c39d 100644
--- a/tests/lib/rules/camelcase.js
+++ b/tests/lib/rules/camelcase.js
@@ -181,6 +181,18 @@ ruleTester.run("camelcase", rule, {
{
code: "function foo({ trailing_ }) {}",
parserOptions: { ecmaVersion: 6 }
+ },
+ {
+ code: "ignored_foo = 0;",
+ options: [{ allow: ["ignored_foo"] }]
+ },
+ {
+ code: "ignored_foo = 0; ignored_bar = 1;",
+ options: [{ allow: ["ignored_foo", "ignored_bar"] }]
+ },
+ {
+ code: "user_id = 0;",
+ options: [{ allow: ["_id$"] }]
}
],
invalid: [
@@ -555,6 +567,26 @@ ruleTester.run("camelcase", rule, {
type: "Identifier"
}
]
+ },
+ {
+ code: "not_ignored_foo = 0;",
+ options: [{ allow: ["ignored_bar"] }],
+ errors: [
+ {
+ message: "Identifier 'not_ignored_foo' is not in camel case.",
+ type: "Identifier"
+ }
+ ]
+ },
+ {
+ code: "not_ignored_foo = 0;",
+ options: [{ allow: ["_id$"] }],
+ errors: [
+ {
+ message: "Identifier 'not_ignored_foo' is not in camel case.",
+ type: "Identifier"
+ }
+ ]
}
]
});
diff --git a/tests/lib/rules/no-extra-bind.js b/tests/lib/rules/no-extra-bind.js
index 4dfec87ac6c9..4e5a08f36608 100644
--- a/tests/lib/rules/no-extra-bind.js
+++ b/tests/lib/rules/no-extra-bind.js
@@ -70,10 +70,32 @@ ruleTester.run("no-extra-bind", rule, {
output: "var a = function() { function c(){ this.d } }",
errors
},
+ {
+ code: "var a = function() { return 1; }.bind(this)",
+ output: "var a = function() { return 1; }",
+ errors
+ },
{
code: "var a = function() { (function(){ (function(){ this.d }.bind(c)) }) }.bind(b)",
output: "var a = function() { (function(){ (function(){ this.d }.bind(c)) }) }",
errors: [{ messageId: "unexpected", type: "CallExpression", column: 71 }]
+ },
+
+ // Should not autofix if bind expression args have side effects
+ {
+ code: "var a = function() {}.bind(b++)",
+ output: null,
+ errors
+ },
+ {
+ code: "var a = function() {}.bind(b())",
+ output: null,
+ errors
+ },
+ {
+ code: "var a = function() {}.bind(b.c)",
+ output: null,
+ errors
}
]
});
diff --git a/tests/lib/rules/no-invalid-regexp.js b/tests/lib/rules/no-invalid-regexp.js
index 305f4e0d3832..0062a5bbc5a7 100644
--- a/tests/lib/rules/no-invalid-regexp.js
+++ b/tests/lib/rules/no-invalid-regexp.js
@@ -46,6 +46,12 @@ ruleTester.run("no-invalid-regexp", rule, {
invalid: [
{ code: "RegExp('[');", errors: [{ message: "Invalid regular expression: /[/: Unterminated character class.", type: "CallExpression" }] },
{ code: "RegExp('.', 'z');", errors: [{ message: "Invalid flags supplied to RegExp constructor 'z'.", type: "CallExpression" }] },
- { code: "new RegExp(')');", errors: [{ message: "Invalid regular expression: /)/: Unmatched ')'.", type: "NewExpression" }] }
+ { code: "new RegExp(')');", errors: [{ message: "Invalid regular expression: /)/: Unmatched ')'.", type: "NewExpression" }] },
+
+ // https://github.com/eslint/eslint/issues/10861
+ {
+ code: String.raw`new RegExp('\\');`,
+ errors: [{ message: "Invalid regular expression: /\\/: \\ at end of pattern.", type: "NewExpression" }]
+ }
]
});
diff --git a/tests/lib/rules/no-tabs.js b/tests/lib/rules/no-tabs.js
index a01d9dc16619..b4989037d308 100644
--- a/tests/lib/rules/no-tabs.js
+++ b/tests/lib/rules/no-tabs.js
@@ -23,7 +23,16 @@ ruleTester.run("no-tabs", rule, {
"function test(){\n}",
"function test(){\n" +
" // sdfdsf \n" +
- "}"
+ "}",
+
+ {
+ code: "\tdoSomething();",
+ options: [{ allowIndentationTabs: true }]
+ },
+ {
+ code: "\t// comment",
+ options: [{ allowIndentationTabs: true }]
+ }
],
invalid: [
{
@@ -31,7 +40,7 @@ ruleTester.run("no-tabs", rule, {
errors: [{
message: ERROR_MESSAGE,
line: 1,
- column: 18
+ column: 17
}]
},
{
@@ -39,7 +48,7 @@ ruleTester.run("no-tabs", rule, {
errors: [{
message: ERROR_MESSAGE,
line: 1,
- column: 6
+ column: 5
}]
},
{
@@ -50,7 +59,7 @@ ruleTester.run("no-tabs", rule, {
errors: [{
message: ERROR_MESSAGE,
line: 2,
- column: 6
+ column: 5
}]
},
{
@@ -61,7 +70,7 @@ ruleTester.run("no-tabs", rule, {
errors: [{
message: ERROR_MESSAGE,
line: 1,
- column: 10
+ column: 9
}]
},
{
@@ -73,14 +82,23 @@ ruleTester.run("no-tabs", rule, {
{
message: ERROR_MESSAGE,
line: 2,
- column: 6
+ column: 5
},
{
message: ERROR_MESSAGE,
line: 3,
- column: 2
+ column: 1
}
]
+ },
+ {
+ code: "\t// Comment with leading tab \t and inline tab",
+ options: [{ allowIndentationTabs: true }],
+ errors: [{
+ message: ERROR_MESSAGE,
+ line: 1,
+ column: 30
+ }]
}
]
});
diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js
index f188f8ce30ae..316df18e09aa 100644
--- a/tests/lib/rules/no-unused-vars.js
+++ b/tests/lib/rules/no-unused-vars.js
@@ -271,7 +271,11 @@ ruleTester.run("no-unused-vars", rule, {
code: "(({a, ...rest}) => rest)",
options: [{ args: "all", ignoreRestSiblings: true }],
parserOptions: { ecmaVersion: 2018 }
- }
+ },
+
+ // https://github.com/eslint/eslint/issues/10952
+ "/*eslint use-every-a:1*/ !function(b, a) { return 1 }"
+
],
invalid: [
{ code: "function foox() { return foox(); }", errors: [definedError("foox")] },
diff --git a/tests/lib/rules/one-var.js b/tests/lib/rules/one-var.js
index 1f6626ad87e1..e5470d2e9ca3 100644
--- a/tests/lib/rules/one-var.js
+++ b/tests/lib/rules/one-var.js
@@ -263,6 +263,13 @@ ruleTester.run("one-var", rule, {
options: ["consecutive"],
parserOptions: { ecmaVersion: 6 }
},
+
+ // https://github.com/eslint/eslint/issues/10784
+ {
+ code: "const foo = require('foo'); const bar = 'bar';",
+ options: [{ const: "consecutive", separateRequires: true }],
+ parserOptions: { ecmaVersion: 6 }
+ },
{
code: "var a = 0, b = 1; var c, d;",
options: [{ initialized: "consecutive", uninitialized: "always" }]
@@ -942,6 +949,17 @@ ruleTester.run("one-var", rule, {
column: 1
}]
},
+ {
+ code: "var a = 1, b = 2",
+ output: "var a = 1; var b = 2",
+ options: ["never"],
+ errors: [{
+ message: "Split 'var' declarations into multiple statements.",
+ type: "VariableDeclaration",
+ line: 1,
+ column: 1
+ }]
+ },
{
code: "var foo = require('foo'), bar;",
output: null,
diff --git a/tests/lib/rules/padding-line-between-statements.js b/tests/lib/rules/padding-line-between-statements.js
index 3f72f8ff2d2a..82147d389d41 100644
--- a/tests/lib/rules/padding-line-between-statements.js
+++ b/tests/lib/rules/padding-line-between-statements.js
@@ -852,6 +852,35 @@ ruleTester.run("padding-line-between-statements", rule, {
]
},
+ //----------------------------------------------------------------------
+ // iife
+ //----------------------------------------------------------------------
+
+ {
+ code: "(function(){\n})()\n\nvar a = 2;",
+ options: [
+ { blankLine: "always", prev: "iife", next: "*" }
+ ]
+ },
+ {
+ code: "+(function(){\n})()\n\nvar a = 2;",
+ options: [
+ { blankLine: "always", prev: "iife", next: "*" }
+ ]
+ },
+ {
+ code: "(function(){\n})()\nvar a = 2;",
+ options: [
+ { blankLine: "never", prev: "iife", next: "*" }
+ ]
+ },
+ {
+ code: "+(function(){\n})()\nvar a = 2;",
+ options: [
+ { blankLine: "never", prev: "iife", next: "*" }
+ ]
+ },
+
//----------------------------------------------------------------------
// import
//----------------------------------------------------------------------
@@ -3371,6 +3400,43 @@ ruleTester.run("padding-line-between-statements", rule, {
errors: [MESSAGE_ALWAYS]
},
+ //----------------------------------------------------------------------
+ // iife
+ //----------------------------------------------------------------------
+
+ {
+ code: "(function(){\n})()\n\nvar a = 2;",
+ output: "(function(){\n})()\nvar a = 2;",
+ options: [
+ { blankLine: "never", prev: "iife", next: "*" }
+ ],
+ errors: [MESSAGE_NEVER]
+ },
+ {
+ code: "+(function(){\n})()\n\nvar a = 2;",
+ output: "+(function(){\n})()\nvar a = 2;",
+ options: [
+ { blankLine: "never", prev: "iife", next: "*" }
+ ],
+ errors: [MESSAGE_NEVER]
+ },
+ {
+ code: "(function(){\n})()\nvar a = 2;",
+ output: "(function(){\n})()\n\nvar a = 2;",
+ options: [
+ { blankLine: "always", prev: "iife", next: "*" }
+ ],
+ errors: [MESSAGE_ALWAYS]
+ },
+ {
+ code: "+(function(){\n})()\nvar a = 2;",
+ output: "+(function(){\n})()\n\nvar a = 2;",
+ options: [
+ { blankLine: "always", prev: "iife", next: "*" }
+ ],
+ errors: [MESSAGE_ALWAYS]
+ },
+
//----------------------------------------------------------------------
// import
//----------------------------------------------------------------------
@@ -4247,6 +4313,14 @@ ruleTester.run("padding-line-between-statements", rule, {
],
errors: [MESSAGE_NEVER]
},
+ {
+ code: "+(function(){\n})()\n\nvar a = 2;",
+ output: "+(function(){\n})()\nvar a = 2;",
+ options: [
+ { blankLine: "never", prev: "block-like", next: "*" }
+ ],
+ errors: [MESSAGE_NEVER]
+ },
{
code: "var a = function() {};\n\nvar b = 2;",
output: "var a = function() {};\nvar b = 2;",
diff --git a/tests/lib/rules/prefer-const.js b/tests/lib/rules/prefer-const.js
index c7b8a93c2be6..f97320234616 100644
--- a/tests/lib/rules/prefer-const.js
+++ b/tests/lib/rules/prefer-const.js
@@ -117,6 +117,54 @@ ruleTester.run("prefer-const", rule, {
parser: fixtureParser("babel-eslint5/destructuring-object-spread")
},
+ // https://github.com/eslint/eslint/issues/8308
+ {
+ code: "let predicate; [typeNode.returnType, predicate] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [typeNode.returnType, ...predicate] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+
+ // intentionally testing empty slot in destructuring assignment
+ code: "let predicate; [typeNode.returnType,, predicate] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [typeNode.returnType=5, predicate] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [[typeNode.returnType=5], predicate] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [[typeNode.returnType, predicate]] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [typeNode.returnType, [predicate]] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [, [typeNode.returnType, predicate]] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [, {foo:typeNode.returnType, predicate}] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let predicate; [, {foo:typeNode.returnType, ...predicate}] = foo();",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+ {
+ code: "let a; const b = {}; ({ a, c: b.c } = func());",
+ parserOptions: { ecmaVersion: 2018 }
+ },
+
// ignoreReadBeforeAssign
{
code: "let x; function foo() { bar(x); } x = 0;",
@@ -380,6 +428,33 @@ ruleTester.run("prefer-const", rule, {
{ message: "'y' is never reassigned. Use 'const' instead.", type: "Identifier" },
{ message: "'z' is never reassigned. Use 'const' instead.", type: "Identifier" }
]
+ },
+
+ // https://github.com/eslint/eslint/issues/8308
+ {
+ code: "let predicate; [, {foo:returnType, predicate}] = foo();",
+ output: null,
+ parserOptions: { ecmaVersion: 2018 },
+ errors: [
+ { message: "'predicate' is never reassigned. Use 'const' instead.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "let predicate; [, {foo:returnType, predicate}, ...bar ] = foo();",
+ output: null,
+ parserOptions: { ecmaVersion: 2018 },
+ errors: [
+ { message: "'predicate' is never reassigned. Use 'const' instead.", type: "Identifier" }
+ ]
+ },
+ {
+ code: "let predicate; [, {foo:returnType, ...predicate} ] = foo();",
+ output: null,
+ parserOptions: { ecmaVersion: 2018 },
+ errors: [
+ { message: "'predicate' is never reassigned. Use 'const' instead.", type: "Identifier" }
+ ]
}
+
]
});
diff --git a/tests/lib/rules/require-atomic-updates.js b/tests/lib/rules/require-atomic-updates.js
index eabd521499cb..4128357773b7 100644
--- a/tests/lib/rules/require-atomic-updates.js
+++ b/tests/lib/rules/require-atomic-updates.js
@@ -51,7 +51,64 @@ ruleTester.run("require-atomic-updates", rule, {
"let foo; function* x() { foo = bar + foo; }",
"async function x() { let foo; bar(() => baz += 1); foo += await amount; }",
"let foo; async function x() { foo = condition ? foo : await bar; }",
- "async function x() { let foo; bar(() => { let foo; blah(foo); }); foo += await result; }"
+ "async function x() { let foo; bar(() => { let foo; blah(foo); }); foo += await result; }",
+ "let foo; async function x() { foo = foo + 1; await bar; }",
+
+
+ /*
+ * Ensure rule doesn't take exponential time in the number of branches
+ * (see https://github.com/eslint/eslint/issues/10893)
+ */
+ `
+ async function foo() {
+ if (1);
+ if (2);
+ if (3);
+ if (4);
+ if (5);
+ if (6);
+ if (7);
+ if (8);
+ if (9);
+ if (10);
+ if (11);
+ if (12);
+ if (13);
+ if (14);
+ if (15);
+ if (16);
+ if (17);
+ if (18);
+ if (19);
+ if (20);
+ }
+ `,
+ `
+ async function foo() {
+ return [
+ 1 ? a : b,
+ 2 ? a : b,
+ 3 ? a : b,
+ 4 ? a : b,
+ 5 ? a : b,
+ 6 ? a : b,
+ 7 ? a : b,
+ 8 ? a : b,
+ 9 ? a : b,
+ 10 ? a : b,
+ 11 ? a : b,
+ 12 ? a : b,
+ 13 ? a : b,
+ 14 ? a : b,
+ 15 ? a : b,
+ 16 ? a : b,
+ 17 ? a : b,
+ 18 ? a : b,
+ 19 ? a : b,
+ 20 ? a : b
+ ];
+ }
+ `
],
invalid: [
@@ -59,6 +116,10 @@ ruleTester.run("require-atomic-updates", rule, {
code: "let foo; async function x() { foo += await amount; }",
errors: [{ messageId: "nonAtomicUpdate", data: { value: "foo" } }]
},
+ {
+ code: "if (1); let foo; async function x() { foo += await amount; }",
+ errors: [{ messageId: "nonAtomicUpdate", data: { value: "foo" } }]
+ },
{
code: "let foo; async function x() { while (condition) { foo += await amount; } }",
errors: [VARIABLE_ERROR]
@@ -138,6 +199,14 @@ ruleTester.run("require-atomic-updates", rule, {
{
code: "async function x() { foo += await bar; }",
errors: [VARIABLE_ERROR]
+ },
+ {
+ code: "let foo = 0; async function x() { foo = (a ? b : foo) + await bar; if (baz); }",
+ errors: [VARIABLE_ERROR]
+ },
+ {
+ code: "let foo = 0; async function x() { foo = (a ? b ? c ? d ? foo : e : f : g : h) + await bar; if (baz); }",
+ errors: [VARIABLE_ERROR]
}
]
});
diff --git a/tests/lib/rules/space-infix-ops.js b/tests/lib/rules/space-infix-ops.js
index 3f98c88d4bb1..34eada46e426 100644
--- a/tests/lib/rules/space-infix-ops.js
+++ b/tests/lib/rules/space-infix-ops.js
@@ -18,8 +18,14 @@ const ruleTester = new RuleTester();
ruleTester.run("space-infix-ops", rule, {
valid: [
"a + b",
+ "a + ++b",
+ "a++ + b",
+ "a++ + ++b",
"a + b",
"(a) + (b)",
+ "((a)) + ((b))",
+ "(((a))) + (((b)))",
+ "a + +b",
"a + (b)",
"a + +(b)",
"a + (+(b))",
@@ -38,7 +44,10 @@ ruleTester.run("space-infix-ops", rule, {
{ code: "function foo(a: number = 0) { }", parserOptions: { ecmaVersion: 6 }, parser: parser("type-annotations/function-parameter-type-annotation") },
{ code: "function foo(): Bar { }", parserOptions: { ecmaVersion: 6 }, parser: parser("type-annotations/function-return-type-annotation") },
{ code: "var foo: Bar = '';", parserOptions: { ecmaVersion: 6 }, parser: parser("type-annotations/variable-declaration-init-type-annotation") },
- { code: "const foo = function(a: number = 0): Bar { };", parserOptions: { ecmaVersion: 6 }, parser: parser("type-annotations/function-expression-type-annotation") }
+ { code: "const foo = function(a: number = 0): Bar { };", parserOptions: { ecmaVersion: 6 }, parser: parser("type-annotations/function-expression-type-annotation") },
+
+ // TypeScript Type Aliases
+ { code: "type Foo = T;", parserOptions: { ecmaVersion: 6 }, parser: parser("typescript-parsers/type-alias") }
],
invalid: [
{
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index 4a860890605d..5a582f97678c 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -640,7 +640,7 @@ describe("RuleTester", () => {
{ code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] }
]
});
- }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have less than 1 items\n\titems: should match some schema in anyOf");
+ }, "Schema for rule no-invalid-schema is invalid:,\titems: should be object\n\titems[0].enum: should NOT have fewer than 1 items\n\titems: should match some schema in anyOf");
});