Repository: eslint/eslint
Pull request #8852 (2017-07-01 11:10:31)

refactor: replace is-my-json-valid with Ajv
**What is the purpose of this pull request? (put an "X" next to item)**

[ ] Documentation update
[ ] Bug fix ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/bug-report.md))
[ ] New rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-proposal.md))
[ ] Changes an existing rule ([template](https://raw.githubusercontent.com/eslint/eslint/master/templates/rule-change-proposal.md))
[ ] Add autofixing to a rule
[ ] Add a CLI option
[X] Add something to the core
[ ] Other, please explain:

**What changes did you make? (Give an overview)**

Replace the ESLint rule validation library from is-my-json-valid to Ajv.

**Is there anything you'd like reviewers to focus on?**

WIP. Will ping when ready for review.

**Motivation**

Ajv has more contributors, more downloads, more regular releases. https://npmcompare.com/compare/ajv,is-my-json-valid summarizes it quite well.

From the technical perspective, the Ajv is the fastest and the most standard compliant (https://github.com/epoberezkin/ajv#performance) implementation of a JSON validator.

It is being used in webpack. I am using it in https://www.npmjs.com/package/table, https://www.npmjs.com/package/isomorphic-webpack, etc.

Finally, Ajv was originally planned to be used for ESLint (https://github.com/eslint/eslint/pull/8295#issuecomment-296556738). The current error message formatting is a bit hacky as a result. Ajv (optionally) provides a custom error message abstraction via https://github.com/epoberezkin/ajv-errors.

eslintbot: Thanks for the pull request, @gajus! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
jsf-clabot: [![CLA assistant check](https://cla.js.foundation/pull/badge/not_signed)](https://cla.js.foundation/eslint/eslint?pullRequest=8852) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you all sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint?pullRequest=8852) before we can accept your contribution.<br/>**1** out of **2** committers have signed the CLA.<br/><br/>:white_check_mark: gajus<br/>:x: epoberezkin
eslintbot: Thanks for the pull request, @epoberezkin! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: Thanks for the pull request, @epoberezkin! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: Thanks for the pull request, @epoberezkin! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: Thanks for the pull request, @epoberezkin! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: Thanks for the pull request, @epoberezkin! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: Thanks for the pull request, @epoberezkin! I took a look to make sure it's ready for merging and found some changes are needed:


* The commit summary needs to begin with a tag (such as `Fix:` or `Update:`). Please check out our [guide](http://eslint.org/docs/developer-guide/contributing/pull-requests#step-2-make-your-changes) for how to properly format your commit summary and [update](http://eslint.org/docs/developer-guide/contributing/pull-requests#updating-the-commit-message) it on this pull request.


Can you please update the pull request to address these?

(More information can be found in our [pull request guide](http://eslint.org/docs/developer-guide/contributing/pull-requests).)
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
gajus: LGTM

Patch:
diff --git a/.github/ISSUE_TEMPLATE.md b/.github/ISSUE_TEMPLATE.md
index 8675e4491..16822fd97 100644
--- a/.github/ISSUE_TEMPLATE.md
+++ b/.github/ISSUE_TEMPLATE.md
@@ -20,14 +20,18 @@
 
 **Please show your full configuration:**
 
-<!-- Paste your configuration below: -->
-```
+<details>
+<summary>Configuration</summary>
 
+<!-- Paste your configuration below: -->
+```js
 
 
 
 ```
 
+</details>
+
 **What did you do? Please include the actual source code causing the issue.**
 
 <!-- Paste the source code below: -->
diff --git a/CONTRIBUTING.md b/CONTRIBUTING.md
index ffe6b574c..3826c28e8 100644
--- a/CONTRIBUTING.md
+++ b/CONTRIBUTING.md
@@ -17,7 +17,7 @@ Before filing an issue, please be sure to read the guidelines for what you're re
 
 ## Contributing Code
 
-Please sign our [Contributor License Agreement](https://contribute.jquery.org/CLA/) and read over the [Pull Request Guidelines](http://eslint.org/docs/developer-guide/contributing/pull-requests).
+Please sign our [Contributor License Agreement](https://js.foundation/CLA) and read over the [Pull Request Guidelines](http://eslint.org/docs/developer-guide/contributing/pull-requests).
 
 ## Full Documentation
 
diff --git a/conf/json-schema-schema.json b/conf/json-schema-schema.json
deleted file mode 100644
index 85eb502a6..000000000
--- a/conf/json-schema-schema.json
+++ /dev/null
@@ -1,150 +0,0 @@
-{
-    "id": "http://json-schema.org/draft-04/schema#",
-    "$schema": "http://json-schema.org/draft-04/schema#",
-    "description": "Core schema meta-schema",
-    "definitions": {
-        "schemaArray": {
-            "type": "array",
-            "minItems": 1,
-            "items": { "$ref": "#" }
-        },
-        "positiveInteger": {
-            "type": "integer",
-            "minimum": 0
-        },
-        "positiveIntegerDefault0": {
-            "allOf": [ { "$ref": "#/definitions/positiveInteger" }, { "default": 0 } ]
-        },
-        "simpleTypes": {
-            "enum": [ "array", "boolean", "integer", "null", "number", "object", "string" ]
-        },
-        "stringArray": {
-            "type": "array",
-            "items": { "type": "string" },
-            "minItems": 1,
-            "uniqueItems": true
-        }
-    },
-    "type": "object",
-    "properties": {
-        "id": {
-            "type": "string",
-            "format": "uri"
-        },
-        "$schema": {
-            "type": "string",
-            "format": "uri"
-        },
-        "title": {
-            "type": "string"
-        },
-        "description": {
-            "type": "string"
-        },
-        "default": {},
-        "multipleOf": {
-            "type": "number",
-            "minimum": 0,
-            "exclusiveMinimum": true
-        },
-        "maximum": {
-            "type": "number"
-        },
-        "exclusiveMaximum": {
-            "type": "boolean",
-            "default": false
-        },
-        "minimum": {
-            "type": "number"
-        },
-        "exclusiveMinimum": {
-            "type": "boolean",
-            "default": false
-        },
-        "maxLength": { "$ref": "#/definitions/positiveInteger" },
-        "minLength": { "$ref": "#/definitions/positiveIntegerDefault0" },
-        "pattern": {
-            "type": "string",
-            "format": "regex"
-        },
-        "additionalItems": {
-            "anyOf": [
-                { "type": "boolean" },
-                { "$ref": "#" }
-            ],
-            "default": {}
-        },
-        "items": {
-            "anyOf": [
-                { "$ref": "#" },
-                { "$ref": "#/definitions/schemaArray" }
-            ],
-            "default": {}
-        },
-        "maxItems": { "$ref": "#/definitions/positiveInteger" },
-        "minItems": { "$ref": "#/definitions/positiveIntegerDefault0" },
-        "uniqueItems": {
-            "type": "boolean",
-            "default": false
-        },
-        "maxProperties": { "$ref": "#/definitions/positiveInteger" },
-        "minProperties": { "$ref": "#/definitions/positiveIntegerDefault0" },
-        "required": { "$ref": "#/definitions/stringArray" },
-        "additionalProperties": {
-            "anyOf": [
-                { "type": "boolean" },
-                { "$ref": "#" }
-            ],
-            "default": {}
-        },
-        "definitions": {
-            "type": "object",
-            "additionalProperties": { "$ref": "#" },
-            "default": {}
-        },
-        "properties": {
-            "type": "object",
-            "additionalProperties": { "$ref": "#" },
-            "default": {}
-        },
-        "patternProperties": {
-            "type": "object",
-            "additionalProperties": { "$ref": "#" },
-            "default": {}
-        },
-        "dependencies": {
-            "type": "object",
-            "additionalProperties": {
-                "anyOf": [
-                    { "$ref": "#" },
-                    { "$ref": "#/definitions/stringArray" }
-                ]
-            }
-        },
-        "enum": {
-            "type": "array",
-            "minItems": 1,
-            "uniqueItems": true
-        },
-        "type": {
-            "anyOf": [
-                { "$ref": "#/definitions/simpleTypes" },
-                {
-                    "type": "array",
-                    "items": { "$ref": "#/definitions/simpleTypes" },
-                    "minItems": 1,
-                    "uniqueItems": true
-                }
-            ]
-        },
-        "allOf": { "$ref": "#/definitions/schemaArray" },
-        "anyOf": { "$ref": "#/definitions/schemaArray" },
-        "oneOf": { "$ref": "#/definitions/schemaArray" },
-        "not": { "$ref": "#" }
-    },
-    "dependencies": {
-        "exclusiveMaximum": [ "maximum" ],
-        "exclusiveMinimum": [ "minimum" ]
-    },
-    "default": {}
-}
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index 67b5e7952..0c35b19fb 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -361,6 +361,8 @@ In the preceding example, the error level is assumed to be the first argument. I
 
 To learn more about JSON Schema, we recommend looking at some [examples](http://json-schema.org/examples.html) to start, and also reading [Understanding JSON Schema](http://spacetelescope.github.io/understanding-json-schema/) (a free ebook).
 
+**Note:** Currently you need to use full JSON Schema object rather than array in case your schema has references ($ref), because in case of array format eslint transforms this array into a single schema without updating references that makes them incorrect (they are ignored).
+
 ### Getting the Source
 
 If your rule needs to get the actual JavaScript source to work with, then use the `sourceCode.getText()` method. This method works as follows:
diff --git a/docs/maintainer-guide/pullrequests.md b/docs/maintainer-guide/pullrequests.md
index fffeb75b5..3b5618389 100644
--- a/docs/maintainer-guide/pullrequests.md
+++ b/docs/maintainer-guide/pullrequests.md
@@ -11,14 +11,14 @@ Anyone, both team members and the public, may leave comments on pull requests.
 When a pull request is opened, the bot will check the following:
 
 1. Has the submitter signed a CLA?
-1. Is the commit message summary in the correct format? Double-check that the tag ("Fix:", "New:", etc.) is correct based on the issue. Documentation-only pull requests do not require an issue.
-1. Does the commit summary reference an issue?
+1. Is the commit message summary in the correct format?
 1. Is the commit summary too long?
 
 The bot will add a comment specifying the problems that it finds. You do not need to look at the pull request any further until those problems have been addressed (there's no need to comment on the pull request to ask the submitter to do what the bot asked - that's why we have the bot!).
 
 Once the bot checks have been satisfied, you check the following:
 
+1. Double-check that the commit message tag ("Fix:", "New:", etc.) is correct based on the issue (or, if no issue is referenced, based on the stated problem).
 1. Does the code follow our conventions (including header comments, JSDoc comments, etc.)? If not, please leave that feedback and reference the conventions document.
 1. For code changes:
     * Are there tests that verify the change? If not, please ask for them.
@@ -38,6 +38,7 @@ Committers may merge a pull request if it is a non-breaking change and is:
 1. A bug fix (for either rules or core)
 1. A dependency upgrade
 1. Related to the build system
+1. A chore
 
 In addition, committers may merge any non-breaking pull request if it has been approved by at least one TSC member.
 
diff --git a/docs/rules/multiline-ternary.md b/docs/rules/multiline-ternary.md
index e331078f3..4faaad79c 100644
--- a/docs/rules/multiline-ternary.md
+++ b/docs/rules/multiline-ternary.md
@@ -26,6 +26,7 @@ Note: The location of the operators is not enforced by this rule. Please see the
 This rule has a string option:
 
 * `"always"` (default) enforces newlines between the operands of a ternary expression.
+* `"always-multiline"` enforces newlines between the operands of a ternary expression if the expression spans multiple lines.
 * `"never"` disallows newlines between the operands of a ternary expression (enforcing that the entire ternary expression is on one line).
 
 ### always
@@ -62,6 +63,50 @@ foo > bar ?
     value3;
 ```
 
+### always-multiline
+
+Examples of **incorrect** code for this rule with the `"always-multiline"` option:
+
+```js
+/*eslint multiline-ternary: ["error", "always"]*/
+
+foo > bar ? value1 :
+    value2;
+
+foo > bar ?
+    value1 : value2;
+
+foo > bar &&
+    bar > baz ? value1 : value2;
+```
+
+Examples of **correct** code for this rule with the `"always-multiline"` option:
+
+```js
+/*eslint multiline-ternary: ["error", "always"]*/
+
+foo > bar ? value1 : value2;
+
+foo > bar ?
+    value1 :
+    value2;
+
+foo > bar ?
+    (baz > qux ? value1 : value2) :
+    value3;
+
+foo > bar ?
+    (baz > qux ?
+        value1 :
+        value2) :
+    value3;
+
+foo > bar &&
+    bar > baz ?
+        value1 :
+        value2;
+```
+
 ### never
 
 Examples of **incorrect** code for this rule with the `"never"` option:
diff --git a/docs/rules/object-curly-newline.md b/docs/rules/object-curly-newline.md
index 40f5368e5..a4907046f 100644
--- a/docs/rules/object-curly-newline.md
+++ b/docs/rules/object-curly-newline.md
@@ -16,7 +16,7 @@ This rule has either a string option:
 Or an object option:
 
 * `"multiline": true` (default) requires line breaks if there are line breaks inside properties or between properties
-* `"minProperties"` requires line breaks if the number of properties is more than the given integer. By default, an error will also be reported if an object contains linebreaks and has fewer properties than the given integer. However, the second behavior is disabled if the `consistent` option is set to `true`
+* `"minProperties"` requires line breaks if the number of properties is at least the given integer. By default, an error will also be reported if an object contains linebreaks and has fewer properties than the given integer. However, the second behavior is disabled if the `consistent` option is set to `true`
 * `"consistent": true` requires that either both curly braces, or neither, directly enclose newlines. Note that enabling this option will also change the behavior of the `minProperties` option. (See `minProperties` above for more information)
 
 You can specify different options for object literals and destructuring assignments:
diff --git a/lib/cli.js b/lib/cli.js
index 530bfbc42..d39847718 100644
--- a/lib/cli.js
+++ b/lib/cli.js
@@ -28,6 +28,17 @@ const debug = require("debug")("eslint:cli");
 // Helpers
 //------------------------------------------------------------------------------
 
+/**
+ * Predicate function for whether or not to apply fixes in quiet mode.
+ * If a message is a warning, do not apply a fix.
+ * @param {LintResult} lintResult The lint result.
+ * @returns {boolean} True if the lint message is an error (and thus should be
+ * autofixed), false otherwise.
+ */
+function quietFixPredicate(lintResult) {
+    return lintResult.severity === 2;
+}
+
 /**
  * Translates the CLI options into the options expected by the CLIEngine.
  * @param {Object} cliOptions The CLI options to translate.
@@ -52,7 +63,7 @@ function translateOptions(cliOptions) {
         cache: cliOptions.cache,
         cacheFile: cliOptions.cacheFile,
         cacheLocation: cliOptions.cacheLocation,
-        fix: cliOptions.fix,
+        fix: cliOptions.fix && (cliOptions.quiet ? quietFixPredicate : true),
         allowInlineConfig: cliOptions.inlineConfig
     };
 }
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 8754485f4..fb5a96564 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -9,7 +9,7 @@
 // Requirements
 //------------------------------------------------------------------------------
 
-const schemaValidator = require("is-my-json-valid"),
+const ajv = require("../util/ajv"),
     configSchema = require("../../conf/config-schema.js"),
     util = require("util");
 
@@ -20,6 +20,7 @@ const validators = {
 //------------------------------------------------------------------------------
 // Private
 //------------------------------------------------------------------------------
+let validateSchema;
 
 /**
  * Gets a complete options schema for a rule.
@@ -79,7 +80,7 @@ function validateRuleSchema(id, localOptions, rulesContext) {
     const schema = getRuleOptionsSchema(id, rulesContext);
 
     if (!validators.rules[id] && schema) {
-        validators.rules[id] = schemaValidator(schema, { verbose: true });
+        validators.rules[id] = ajv.compile(schema);
     }
 
     const validateRule = validators.rules[id];
@@ -87,7 +88,7 @@ function validateRuleSchema(id, localOptions, rulesContext) {
     if (validateRule) {
         validateRule(localOptions);
         if (validateRule.errors) {
-            throw new Error(validateRule.errors.map(error => `\tValue "${error.value}" ${error.message}.\n`).join(""));
+            throw new Error(validateRule.errors.map(error => `\tValue "${error.data}" ${error.message}.\n`).join(""));
         }
     }
 }
@@ -158,19 +159,23 @@ function validateRules(rulesConfig, source, rulesContext) {
  * @returns {string} Formatted error message
  */
 function formatErrors(errors) {
-
     return errors.map(error => {
-        if (error.message === "has additional properties") {
-            return `Unexpected top-level property "${error.value.replace(/^data\./, "")}"`;
+        if (error.keyword === "additionalProperties") {
+            const formattedPropertyPath = error.dataPath.length ? `${error.dataPath.slice(1)}.${error.params.additionalProperty}` : error.params.additionalProperty;
+
+            return `Unexpected top-level property "${formattedPropertyPath}"`;
         }
-        if (error.message === "is the wrong type") {
-            const formattedField = error.field.replace(/^data\./, "");
-            const formattedExpectedType = typeof error.type === "string" ? error.type : error.type.join("/");
-            const formattedValue = JSON.stringify(error.value);
+        if (error.keyword === "type") {
+            const formattedField = error.dataPath.slice(1);
+            const formattedExpectedType = Array.isArray(error.schema) ? error.schema.join("/") : error.schema;
+            const formattedValue = JSON.stringify(error.data);
 
             return `Property "${formattedField}" is the wrong type (expected ${formattedExpectedType} but got \`${formattedValue}\`)`;
         }
-        return `"${error.field.replace(/^(data\.)/, "")}" ${error.message}. Value: ${JSON.stringify(error.value)}`;
+
+        const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
+
+        return `"${field}" ${error.message}. Value: ${JSON.stringify(error.data)}`;
     }).map(message => `\t- ${message}.\n`).join("");
 }
 
@@ -181,10 +186,10 @@ function formatErrors(errors) {
  * @returns {void}
  */
 function validateConfigSchema(config, source) {
-    const validator = schemaValidator(configSchema, { verbose: true });
+    validateSchema = validateSchema || ajv.compile(configSchema);
 
-    if (!validator(config)) {
-        throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validator.errors)}`);
+    if (!validateSchema(config)) {
+        throw new Error(`${source}:\n\tESLint configuration is invalid:\n${formatErrors(validateSchema.errors)}`);
     }
 }
 
diff --git a/lib/rules/comma-dangle.js b/lib/rules/comma-dangle.js
index 17066d947..ddcc0cd22 100644
--- a/lib/rules/comma-dangle.js
+++ b/lib/rules/comma-dangle.js
@@ -81,49 +81,49 @@ module.exports = {
             category: "Stylistic Issues",
             recommended: false
         },
-
         fixable: "code",
-
-        schema: [
-            {
-                defs: {
-                    value: {
-                        enum: [
-                            "always",
-                            "always-multiline",
-                            "only-multiline",
-                            "never"
-                        ]
-                    },
-                    valueWithIgnore: {
-                        anyOf: [
-                            {
-                                $ref: "#/defs/value"
-                            },
-                            {
-                                enum: ["ignore"]
-                            }
-                        ]
-                    }
+        schema: {
+            definitions: {
+                value: {
+                    enum: [
+                        "always-multiline",
+                        "always",
+                        "never",
+                        "only-multiline"
+                    ]
                 },
-                anyOf: [
-                    {
-                        $ref: "#/defs/value"
-                    },
-                    {
-                        type: "object",
-                        properties: {
-                            arrays: { $refs: "#/defs/valueWithIgnore" },
-                            objects: { $refs: "#/defs/valueWithIgnore" },
-                            imports: { $refs: "#/defs/valueWithIgnore" },
-                            exports: { $refs: "#/defs/valueWithIgnore" },
-                            functions: { $refs: "#/defs/valueWithIgnore" }
+                valueWithIgnore: {
+                    enum: [
+                        "always-multiline",
+                        "always",
+                        "ignore",
+                        "never",
+                        "only-multiline"
+                    ]
+                }
+            },
+            type: "array",
+            items: [
+                {
+                    oneOf: [
+                        {
+                            $ref: "#/definitions/value"
                         },
-                        additionalProperties: false
-                    }
-                ]
-            }
-        ]
+                        {
+                            type: "object",
+                            properties: {
+                                arrays: { $ref: "#/definitions/valueWithIgnore" },
+                                objects: { $ref: "#/definitions/valueWithIgnore" },
+                                imports: { $ref: "#/definitions/valueWithIgnore" },
+                                exports: { $ref: "#/definitions/valueWithIgnore" },
+                                functions: { $ref: "#/definitions/valueWithIgnore" }
+                            },
+                            additionalProperties: false
+                        }
+                    ]
+                }
+            ]
+        }
     },
 
     create(context) {
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 6babe29c9..c8195c7d7 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -1030,7 +1030,7 @@ module.exports = {
                 const nodeTokens = getTokensAndComments(node);
                 const tokensFromOperator = nodeTokens.slice(lodash.sortedIndexBy(nodeTokens, operator, token => token.range[0]));
 
-                offsets.setDesiredOffsets(tokensFromOperator, sourceCode.getFirstToken(node.left), 1);
+                offsets.setDesiredOffsets(tokensFromOperator, sourceCode.getLastToken(node.left), 1);
                 offsets.ignoreToken(tokensFromOperator[0]);
                 offsets.ignoreToken(tokensFromOperator[1]);
             },
@@ -1325,12 +1325,12 @@ module.exports = {
             VariableDeclarator(node) {
                 if (node.init) {
                     const equalOperator = sourceCode.getTokenBefore(node.init, astUtils.isNotOpeningParenToken);
-                    const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator);
+                    const tokensAfterOperator = sourceCode.getTokensAfter(equalOperator, token => token.range[1] <= node.range[1]);
 
                     offsets.ignoreToken(equalOperator);
-                    offsets.ignoreToken(tokenAfterOperator);
-                    offsets.matchIndentOf(equalOperator, tokenAfterOperator);
-                    offsets.matchIndentOf(sourceCode.getFirstToken(node), equalOperator);
+                    offsets.ignoreToken(tokensAfterOperator[0]);
+                    offsets.setDesiredOffsets(tokensAfterOperator, equalOperator, 1);
+                    offsets.matchIndentOf(sourceCode.getLastToken(node.id), equalOperator);
                 }
             },
 
@@ -1375,13 +1375,14 @@ module.exports = {
 
             JSXExpressionContainer(node) {
                 const openingCurly = sourceCode.getFirstToken(node);
-                const firstExpressionToken = sourceCode.getFirstToken(node.expression);
+                const closingCurly = sourceCode.getLastToken(node);
 
-                if (firstExpressionToken) {
-                    offsets.setDesiredOffset(firstExpressionToken, openingCurly, 1);
-                }
-
-                offsets.matchIndentOf(openingCurly, sourceCode.getLastToken(node));
+                offsets.setDesiredOffsets(
+                    sourceCode.getTokensBetween(openingCurly, closingCurly, { includeComments: true }),
+                    openingCurly,
+                    1
+                );
+                offsets.matchIndentOf(openingCurly, closingCurly);
             },
 
             "Program:exit"() {
diff --git a/lib/rules/multiline-ternary.js b/lib/rules/multiline-ternary.js
index de19bd43f..a74f241d8 100644
--- a/lib/rules/multiline-ternary.js
+++ b/lib/rules/multiline-ternary.js
@@ -20,13 +20,15 @@ module.exports = {
         },
         schema: [
             {
-                enum: ["always", "never"]
+                enum: ["always", "always-multiline", "never"]
             }
         ]
     },
 
     create(context) {
-        const multiline = context.options[0] !== "never";
+        const option = context.options[0];
+        const multiline = option !== "never";
+        const allowSingleLine = option === "always-multiline";
 
         //--------------------------------------------------------------------------
         // Helpers
@@ -69,6 +71,10 @@ module.exports = {
                         reportError(node.consequent, node, false);
                     }
                 } else {
+                    if (allowSingleLine && node.loc.start.line === node.loc.end.line) {
+                        return;
+                    }
+
                     if (areTestAndConsequentOnSameLine) {
                         reportError(node.test, node, true);
                     }
diff --git a/lib/rules/no-sync.js b/lib/rules/no-sync.js
index 885b1eb62..06305969a 100644
--- a/lib/rules/no-sync.js
+++ b/lib/rules/no-sync.js
@@ -19,14 +19,26 @@ module.exports = {
             recommended: false
         },
 
-        schema: []
+        schema: [
+            {
+                type: "object",
+                properties: {
+                    allowAtRootLevel: {
+                        type: "boolean"
+                    }
+                },
+                additionalProperties: false
+            }
+        ]
     },
 
     create(context) {
+        const selector = context.options[0] && context.options[0].allowAtRootLevel
+            ? ":function MemberExpression[property.name=/.*Sync$/]"
+            : "MemberExpression[property.name=/.*Sync$/]";
 
         return {
-
-            "MemberExpression[property.name=/.*Sync$/]"(node) {
+            [selector](node) {
                 context.report({
                     node,
                     message: "Unexpected sync method: '{{propertyName}}'.",
diff --git a/lib/util/ajv.js b/lib/util/ajv.js
new file mode 100644
index 000000000..f9e8b9853
--- /dev/null
+++ b/lib/util/ajv.js
@@ -0,0 +1,29 @@
+/**
+ * @fileoverview The instance of Ajv validator.
+ * @author Evgeny Poberezkin
+ */
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const Ajv = require("ajv"),
+    metaSchema = require("ajv/lib/refs/json-schema-draft-04.json");
+
+//------------------------------------------------------------------------------
+// Public Interface
+//------------------------------------------------------------------------------
+
+const ajv = new Ajv({
+    meta: false,
+    validateSchema: false,
+    missingRefs: "ignore",
+    verbose: true
+});
+
+ajv.addMetaSchema(metaSchema);
+// eslint-disable-next-line no-underscore-dangle
+ajv._opts.defaultMeta = metaSchema.id;
+
+module.exports = ajv;
diff --git a/package.json b/package.json
index 1f459dea8..9793252de 100644
--- a/package.json
+++ b/package.json
@@ -34,6 +34,7 @@
   "homepage": "http://eslint.org",
   "bugs": "https://github.com/eslint/eslint/issues/",
   "dependencies": {
+    "ajv": "^5.2.0",
     "babel-code-frame": "^6.22.0",
     "chalk": "^1.1.3",
     "concat-stream": "^1.6.0",
@@ -50,7 +51,6 @@
     "ignore": "^3.3.3",
     "imurmurhash": "^0.1.4",
     "inquirer": "^3.0.6",
-    "is-my-json-valid": "^2.16.0",
     "is-resolvable": "^1.0.0",
     "js-yaml": "^3.8.4",
     "json-stable-stringify": "^1.0.1",


Test patch:
diff --git a/lib/testers/rule-tester.js b/lib/testers/rule-tester.js
index 9d747e96a..fc6781fdc 100644
--- a/lib/testers/rule-tester.js
+++ b/lib/testers/rule-tester.js
@@ -44,10 +44,9 @@ const lodash = require("lodash"),
     assert = require("assert"),
     util = require("util"),
     validator = require("../config/config-validator"),
-    validate = require("is-my-json-valid"),
+    ajv = require("../util/ajv"),
     Linter = require("../linter"),
     Environments = require("../config/environments"),
-    metaSchema = require("../../conf/json-schema-schema.json"),
     SourceCodeFixer = require("../util/source-code-fixer");
 
 //------------------------------------------------------------------------------
@@ -73,8 +72,6 @@ const RuleTesterParameters = [
     "output"
 ];
 
-const validateSchema = validate(metaSchema, { verbose: true });
-
 const hasOwnProperty = Function.call.bind(Object.hasOwnProperty);
 
 /**
@@ -318,12 +315,16 @@ class RuleTester {
             const schema = validator.getRuleOptionsSchema(ruleName, linter.rules);
 
             if (schema) {
-                validateSchema(schema);
+                ajv.validateSchema(schema);
+
+                if (ajv.errors) {
+                    const errors = ajv.errors.map(error => {
+                        const field = error.dataPath[0] === "." ? error.dataPath.slice(1) : error.dataPath;
+
+                        return `\t${field}: ${error.message}`;
+                    }).join("\n");
 
-                if (validateSchema.errors) {
-                    throw new Error([
-                        `Schema for rule ${ruleName} is invalid:`
-                    ].concat(validateSchema.errors.map(error => `\t${error.field}: ${error.message}`)).join("\n"));
+                    throw new Error([`Schema for rule ${ruleName} is invalid:`, errors]);
                 }
             }
 
diff --git a/tests/bin/eslint.js b/tests/bin/eslint.js
index c7e97a00c..7c5cd6552 100644
--- a/tests/bin/eslint.js
+++ b/tests/bin/eslint.js
@@ -112,6 +112,7 @@ describe("bin/eslint.js", () => {
         const tempFilePath = `${fixturesPath}/temp.js`;
         const startingText = fs.readFileSync(`${fixturesPath}/left-pad.js`).toString();
         const expectedFixedText = fs.readFileSync(`${fixturesPath}/left-pad-expected.js`).toString();
+        const expectedFixedTextQuiet = fs.readFileSync(`${fixturesPath}/left-pad-expected-quiet.js`).toString();
 
         beforeEach(() => {
             fs.writeFileSync(tempFilePath, startingText);
@@ -127,6 +128,17 @@ describe("bin/eslint.js", () => {
             return Promise.all([exitCodeAssertion, outputFileAssertion]);
         });
 
+        it("has exit code 0, fixes errors in a file, and does not report or fix warnings if --quiet and --fix are used", () => {
+            const child = runESLint(["--fix", "--quiet", "--no-eslintrc", "--no-ignore", tempFilePath]);
+            const exitCodeAssertion = assertExitCode(child, 0);
+            const stdoutAssertion = getStdout(child).then(stdout => assert.strictEqual(stdout, ""));
+            const outputFileAssertion = awaitExit(child).then(() => {
+                assert.strictEqual(fs.readFileSync(tempFilePath).toString(), expectedFixedTextQuiet);
+            });
+
+            return Promise.all([exitCodeAssertion, stdoutAssertion, outputFileAssertion]);
+        });
+
         it("has exit code 1 and fixes a file if not all rules can be fixed", () => {
             const child = runESLint(["--fix", "--no-eslintrc", "--no-ignore", "--rule", "max-len: [2, 10]", tempFilePath]);
             const exitCodeAssertion = assertExitCode(child, 1);
diff --git a/tests/conf/config-schema.js b/tests/conf/config-schema.js
new file mode 100644
index 000000000..3fdb4bb03
--- /dev/null
+++ b/tests/conf/config-schema.js
@@ -0,0 +1,26 @@
+/**
+ * @fileoverview Tests for config-schema.
+ * @author Evgeny Poberezkin
+ */
+
+"use strict";
+
+//------------------------------------------------------------------------------
+// Requirements
+//------------------------------------------------------------------------------
+
+const ajv = require("../../lib/util/ajv"),
+    configSchema = require("../../conf/config-schema.js"),
+    assert = require("assert");
+
+//------------------------------------------------------------------------------
+// Tests
+//------------------------------------------------------------------------------
+
+describe("config-schema", () => {
+    it("should be valid against meta-schema", () => {
+        const valid = ajv.validateSchema(configSchema);
+
+        assert.strictEqual(valid, true);
+    });
+});
diff --git a/tests/fixtures/autofix-integration/left-pad-expected-quiet.js b/tests/fixtures/autofix-integration/left-pad-expected-quiet.js
new file mode 100644
index 000000000..b189698bf
--- /dev/null
+++ b/tests/fixtures/autofix-integration/left-pad-expected-quiet.js
@@ -0,0 +1,33 @@
+/* eslint dot-notation: 2 */
+/* eslint indent: [2, 2] */
+/* eslint no-extra-parens: 2 */
+/* eslint no-implicit-coercion: 2 */
+/* eslint no-whitespace-before-property: 2 */
+/* eslint quotes: [2, "single"] */
+/* eslint semi: 2 */
+/* eslint semi-spacing: 2 */
+/* eslint space-before-function-paren: 2 */
+/* eslint space-before-blocks: 1 */
+
+/*
+* left-pad@0.0.3
+* WTFPL https://github.com/stevemao/left-pad/blob/aff6d744155a70b81f09effb8185a1564f348462/COPYING
+*/
+
+module.exports = leftpad;
+
+function leftpad (str, len, ch){
+  str = String(str);
+
+  var i = -1;
+
+  ch || (ch = ' ');
+  len = len - str.length;
+
+
+  while (++i < len) {
+    str = ch + str;
+  }
+
+  return str;
+}
diff --git a/tests/fixtures/autofix-integration/left-pad-expected.js b/tests/fixtures/autofix-integration/left-pad-expected.js
index 9e2a8321d..24da3ce17 100644
--- a/tests/fixtures/autofix-integration/left-pad-expected.js
+++ b/tests/fixtures/autofix-integration/left-pad-expected.js
@@ -7,6 +7,7 @@
 /* eslint semi: 2 */
 /* eslint semi-spacing: 2 */
 /* eslint space-before-function-paren: 2 */
+/* eslint space-before-blocks: 1 */
 
 /*
 * left-pad@0.0.3
diff --git a/tests/fixtures/autofix-integration/left-pad.js b/tests/fixtures/autofix-integration/left-pad.js
index 1dd798ff8..7b673577e 100644
--- a/tests/fixtures/autofix-integration/left-pad.js
+++ b/tests/fixtures/autofix-integration/left-pad.js
@@ -7,6 +7,7 @@
 /* eslint semi: 2 */
 /* eslint semi-spacing: 2 */
 /* eslint space-before-function-paren: 2 */
+/* eslint space-before-blocks: 1 */
 
 /*
 * left-pad@0.0.3
@@ -15,7 +16,7 @@
 
 module.exports = (leftpad)
 
-   function leftpad(str, len, ch) {
+   function leftpad(str, len, ch){
   str = ("" + str);
 
          var i = -1 ;
diff --git a/tests/lib/cli.js b/tests/lib/cli.js
index a0bcb37cf..b75640d4a 100644
--- a/tests/lib/cli.js
+++ b/tests/lib/cli.js
@@ -804,7 +804,7 @@ describe("cli", () => {
 
         });
 
-        it("should rewrite files when in fix mode and quiet mode", () => {
+        it("should provide fix predicate and rewrite files when in fix mode and quiet mode", () => {
 
             const report = {
                 errorCount: 0,
@@ -822,7 +822,7 @@ describe("cli", () => {
             };
 
             // create a fake CLIEngine to test with
-            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: true }));
+            const fakeCLIEngine = sandbox.mock().withExactArgs(sinon.match({ fix: sinon.match.func }));
 
             fakeCLIEngine.prototype = leche.fake(CLIEngine.prototype);
             sandbox.stub(fakeCLIEngine.prototype, "executeOnFiles").returns(report);
diff --git a/tests/lib/config/config-validator.js b/tests/lib/config/config-validator.js
index d220fdb93..5e42823b2 100644
--- a/tests/lib/config/config-validator.js
+++ b/tests/lib/config/config-validator.js
@@ -350,7 +350,7 @@ describe("Validator", () => {
 
                 const fn = validator.validate.bind(null, { rules: { "mock-no-options-rule": [2, "extra"] } }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue \"extra\" has more items than allowed.\n");
+                assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-no-options-rule\" is invalid:\n\tValue \"extra\" should NOT have more than 0 items.\n");
             });
         });
 
@@ -370,31 +370,31 @@ describe("Validator", () => {
             it("should throw if override does not specify files", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ rules: {} }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- \"overrides.0.files\" is required. Value: {\"rules\":{}}.\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- \"overrides[0]\" should have required property 'files'. Value: {\"rules\":{}}.\n");
             });
 
             it("should throw if override has an empty files array", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: [] }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- \"overrides.0.files\" no (or more than one) schemas match. Value: [].\n");
+                assert.throws(fn, "tests:\n\tESLint configuration 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");
             });
 
             it("should throw if override has nested overrides", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", overrides: [{ files: "*", rules: {} }] }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[j].overrides\".\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].overrides\".\n");
             });
 
             it("should throw if override extends", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", extends: "eslint-recommended" }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[j].extends\".\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].extends\".\n");
             });
 
             it("should throw if override tries to set root", () => {
                 const fn = validator.validate.bind(null, { overrides: [{ files: "*", root: "true" }] }, "tests", linter.rules, linter.environments);
 
-                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[j].root\".\n");
+                assert.throws(fn, "tests:\n\tESLint configuration is invalid:\n\t- Unexpected top-level property \"overrides[0].root\".\n");
             });
         });
 
@@ -450,13 +450,13 @@ describe("Validator", () => {
         it("should throw for incorrect configuration values", () => {
             const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "frist"], "tests", linter.rules);
 
-            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" must be an enum value.\n");
+            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"frist\" should be equal to one of the allowed values.\n");
         });
 
         it("should throw for too many configuration values", () => {
             const fn = validator.validateRuleOptions.bind(null, "mock-rule", [2, "first", "second"], "tests", linter.rules);
 
-            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"first,second\" has more items than allowed.\n");
+            assert.throws(fn, "tests:\n\tConfiguration for rule \"mock-rule\" is invalid:\n\tValue \"first,second\" should NOT have more than 1 items.\n");
         });
 
     });
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 88ce63cb1..d52292abc 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -71,7 +71,7 @@ function getVariable(scope, name) {
 // Tests
 //------------------------------------------------------------------------------
 
-describe("eslint", () => {
+describe("Linter", () => {
     const filename = "filename.js";
     let sandbox, linter;
 
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index dc65f57d4..17e5b5357 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -67,7 +67,7 @@ function unIndent(strings) {
     return lines.map(line => line.slice(minLineIndent)).join("\n");
 }
 
-const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 6, ecmaFeatures: { jsx: true } } });
+const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 8, ecmaFeatures: { jsx: true } } });
 
 ruleTester.run("indent", rule, {
     valid: [
@@ -3891,6 +3891,67 @@ ruleTester.run("indent", rule, {
             `
         },
 
+        // https://github.com/eslint/eslint/issues/8815
+        {
+            code: unIndent`
+                async function test() {
+                    const {
+                        foo,
+                        bar,
+                    } = await doSomethingAsync(
+                        1,
+                        2,
+                        3,
+                    );
+                }
+            `
+        },
+        {
+            code: unIndent`
+                function* test() {
+                    const {
+                        foo,
+                        bar,
+                    } = yield doSomethingAsync(
+                        1,
+                        2,
+                        3,
+                    );
+                }
+            `
+        },
+        {
+            code: unIndent`
+                ({
+                    a: b
+                } = +foo(
+                    bar
+                ));
+            `
+        },
+        {
+            code: unIndent`
+                const {
+                    foo,
+                    bar,
+                } = typeof foo(
+                    1,
+                    2,
+                    3,
+                );
+            `
+        },
+        {
+            code: unIndent`
+                const {
+                    foo,
+                    bar,
+                } = +(
+                    foo
+                );
+            `
+        },
+
         //----------------------------------------------------------------------
         // JSX tests
         // https://github.com/eslint/eslint/issues/8425
@@ -4649,6 +4710,44 @@ ruleTester.run("indent", rule, {
                     </span>
                 )
             `
+        },
+        {
+            code: unIndent`
+                <div>
+                    {
+                        /* foo */
+                    }
+                </div>
+            `
+        },
+
+        // https://github.com/eslint/eslint/issues/8832
+        {
+            code: unIndent`
+                <div>
+                    {
+                        (
+                            1
+                        )
+                    }
+                </div>
+            `
+        },
+        {
+            code: unIndent`
+                function A() {
+                    return (
+                        <div>
+                            {
+                                b && (
+                                    <div>
+                                    </div>
+                                )
+                            }
+                        </div>
+                    );
+                }
+            `
         }
     ],
 
@@ -8769,6 +8868,44 @@ ruleTester.run("indent", rule, {
                 )
             `,
             errors: expectedErrors([[5, 4, 8, "Punctuator"], [6, 4, 8, "Punctuator"], [7, 0, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                <div>
+                    {
+                    (
+                        1
+                    )
+                    }
+                </div>
+            `,
+            output: unIndent`
+                <div>
+                    {
+                        (
+                            1
+                        )
+                    }
+                </div>
+            `,
+            errors: expectedErrors([[3, 8, 4, "Punctuator"], [4, 12, 8, "Numeric"], [5, 8, 4, "Punctuator"]])
+        },
+        {
+            code: unIndent`
+                <div>
+                    {
+                      /* foo */
+                    }
+                </div>
+            `,
+            output: unIndent`
+                <div>
+                    {
+                        /* foo */
+                    }
+                </div>
+            `,
+            errors: expectedErrors([3, 8, 6, "Block"])
         }
     ]
 });
diff --git a/tests/lib/rules/multiline-ternary.js b/tests/lib/rules/multiline-ternary.js
index 74221b6c0..30750246c 100644
--- a/tests/lib/rules/multiline-ternary.js
+++ b/tests/lib/rules/multiline-ternary.js
@@ -37,6 +37,17 @@ ruleTester.run("multiline-ternary", rule, {
         { code: "a\n? b\n? c\n: d\n: e", options: ["always"] },
         { code: "a\n? (b\n? c\n: d)\n: e", options: ["always"] },
 
+        // "always-multiline"
+        { code: "a\n? b\n: c", options: ["always-multiline"] },
+        { code: "a ?\nb :\nc", options: ["always-multiline"] },
+        { code: "a\n? b\n? c\n: d\n: e", options: ["always-multiline"] },
+        { code: "a\n? (b\n? c\n: d)\n: e", options: ["always-multiline"] },
+        { code: "a ? b : c", options: ["always-multiline"] },
+        { code: "a ? b ? c : d : e", options: ["always-multiline"] },
+        { code: "a ? (b ? c : d) : e", options: ["always-multiline"] },
+        { code: "a\n? (b ? c : d)\n: e", options: ["always-multiline"] },
+        { code: "a ?\n(b ? c : d) :\ne", options: ["always-multiline"] },
+
         // "never"
         { code: "a ? b : c", options: ["never"] },
         { code: "a ? b ? c : d : e", options: ["never"] },
@@ -282,6 +293,137 @@ ruleTester.run("multiline-ternary", rule, {
             }]
         },
 
+        // "always-multiline"
+        {
+            code: "a\n? b : c",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 3
+            }]
+        },
+        {
+            code: "a ? b\n: c",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            }]
+        },
+        {
+            code: "a &&\nb ? c : d",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 5
+            }]
+        },
+        {
+            code: "a ? b +\nc : d",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 1,
+                column: 5
+            }]
+        },
+        {
+            code: "a ? b : c +\nd",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 1,
+                column: 5
+            }]
+        },
+        {
+            code: "a ?\n(b ? c : d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            }]
+        },
+        {
+            code: "a ? (b ? c : d) :\ne",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            }]
+        },
+        {
+            code: "a ? (b\n? c\n: d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedTestConsMsg,
+                line: 1,
+                column: 1
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 1,
+                column: 6
+            }]
+        },
+        {
+            code: "a ?\n(b ? c\n: d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            },
+            {
+                message: expectedTestConsMsg,
+                line: 2,
+                column: 2
+            }]
+        },
+        {
+            code: "a ?\n(b\n? c : d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            },
+            {
+                message: expectedConsAltMsg,
+                line: 3,
+                column: 3
+            }]
+        },
+        {
+            code: "a ?\n(b\n? c\n : d) : e",
+            options: ["always-multiline"],
+            errors: [{
+                message: expectedConsAltMsg,
+                line: 2,
+                column: 2
+            }]
+        },
+
         // "never"
         {
             code: "a\n? b : c",
diff --git a/tests/lib/rules/no-sync.js b/tests/lib/rules/no-sync.js
index 349e198cb..026db6862 100644
--- a/tests/lib/rules/no-sync.js
+++ b/tests/lib/rules/no-sync.js
@@ -20,10 +20,18 @@ const ruleTester = new RuleTester();
 
 ruleTester.run("no-sync", rule, {
     valid: [
-        "var foo = fs.foo.foo();"
+        "var foo = fs.foo.foo();",
+        { code: "var foo = fs.fooSync;", options: [{ allowAtRootLevel: true }] },
+        { code: "if (true) {fs.fooSync();}", options: [{ allowAtRootLevel: true }] }
     ],
     invalid: [
         { code: "var foo = fs.fooSync();", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
-        { code: "var foo = fs.fooSync;", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] }
+        { code: "var foo = fs.fooSync();", options: [{ allowAtRootLevel: false }], errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "if (true) {fs.fooSync();}", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "var foo = fs.fooSync;", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "function someFunction() {fs.fooSync();}", errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "function someFunction() {fs.fooSync();}", options: [{ allowAtRootLevel: true }], errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] },
+        { code: "var a = function someFunction() {fs.fooSync();}", options: [{ allowAtRootLevel: true }], errors: [{ message: "Unexpected sync method: 'fooSync'.", type: "MemberExpression" }] }
+
     ]
 });
diff --git a/tests/lib/testers/rule-tester.js b/tests/lib/testers/rule-tester.js
index b2ca45e9e..86bf40cde 100644
--- a/tests/lib/testers/rule-tester.js
+++ b/tests/lib/testers/rule-tester.js
@@ -619,7 +619,7 @@ describe("RuleTester", () => {
                     { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected nothing." }] }
                 ]
             });
-        }, /Schema for rule .* is invalid/);
+        }, "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");
 
     });
 
@@ -635,7 +635,7 @@ describe("RuleTester", () => {
                     { code: "var answer = 6 * 7;", options: ["bar"], errors: [{ message: "Expected foo." }] }
                 ]
             });
-        }, /Value "bar" must be an enum value./);
+        }, /Value "bar" should be equal to one of the allowed values./);
 
     });