Repository: eslint/eslint
Pull request #8467 (2017-04-16 03:13:16)

Docs: list another related rule in no-undefined
**What is the purpose of this pull request? (put an "X" next to item)**

[x] 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
[ ] Add something to the core
[ ] Other, please explain:

**What changes did you make? (Give an overview)**

Addded no-shadow-restricted-names as a related rule to no-undefined's documentation, because some people might want to flag all shadowing of undefined (as the former does) and then not use the `no-undefined` rule.

**Is there anything you'd like reviewers to focus on?**




jsf-clabot: [![CLA assistant check](https://cla.js.foundation/pull/badge/not_signed)](https://cla.js.foundation/eslint/eslint?pullRequest=8467) <br/>Thank you for your submission, we really appreciate it. Like many open source projects, we ask that you sign our [Contributor License Agreement](https://cla.js.foundation/eslint/eslint?pullRequest=8467) before we can accept your contribution.<br/>
mention-bot: @ethanresnick, thanks for your PR! By analyzing the history of the files in this pull request, we identified @nzakas, @michaelficarra and @BYK to be potential reviewers.
eslintbot: LGTM
platinumazure: Hi @ethanresnick, thanks for the PR!

Hmm... This is a tough one for me. It's a pretty limited sort of relationship and potentially one should also link `no-restricted-globals` for non-shadowing cases, and then you have two tenuous "related rules".

I wonder if there should instead be a paragraph somewhere higher in the documentation which says all uses of `undefined` are forbidden by this rule and that anyone who just wants to avoid reassignments to undefined should use rules X and Y...

If others on the team think this is okay as is, I definitely don't want to stand in the way. I just wanted to throw out a thought, that's all.
not-an-aardvark: @platinumazure If it helps as a datapoint, I hadn't realized that `no-shadow-restricted-names` existed, so this PR was useful to me. 😄 
platinumazure: @not-an-aardvark Thanks, makes sense.

I'm just worried that the change might be incomplete. `no-shadow-restricted-names` picks up on new variable declarations that shadow the `undefined` global:

```js
var undefined = "true";
```

It won't pick up on reassignments:

```js
undefined = "true";
```

So I think we should figure out what we want to recommend for that case and include that in this change.
not-an-aardvark: I agree that it's not a drop-in replacement, but the rules do share *some* functionality, so I think it's reasonable to call them "related".
platinumazure: @not-an-aardvark I have no problem with including no-shadow-restricted-names as a related rule anymore, I promise. I just want to make sure the reassign case is also covered.
ethanresnick: @platinumazure How would you suggest I edit this to cover the reassign case?
platinumazure: @ethanresnick Looks like `no-global-assign` will treat `undefined` as a "read-only" (or at least "shouldn't be modified") global by default, so I think it might be sufficient to add that users might also consider using `no-global-assign` as well as `no-shadow-restricted-names` in order to avoid reassignments to or shadowing of `undefined` but allowing reads of that same variable.

So if you just add `no-global-assign` to the Related Rules and a quick paragraph in the rule details about using those two rules to block reassigns to `undefined` while still allowing the use of the rule, then I'll be happy. :smile:
eslintbot: LGTM
ethanresnick: @platinumazure That makes sense. I've updated the PR to reference no-global-assign too. In the process of adding the new paragraph, I tried to clean up the prose a bit too, so things would still flow.
eslintbot: LGTM
eslintbot: LGTM
platinumazure: Looks like you have some markdown linting errors (trailing space); please review and fix at your convenience and let us know if you need help. Thanks!
eslintbot: LGTM

Patch:
diff --git a/CHANGELOG.md b/CHANGELOG.md
index 98d4702a6..84d0fd730 100644
--- a/CHANGELOG.md
+++ b/CHANGELOG.md
@@ -1,3 +1,23 @@
+v4.0.0-alpha.1 - April 21, 2017
+
+* b0dadfe3 Docs: Update comments section of Migrating to v4.0.0 (#8486) (Kai Cataldo)
+* b337738f Update: Add `consistent` option to `object-curly-newline` (fixes #6488) (#7720) (Evilebot Tnawi)
+* 53fefb3b Update: add fix for no-confusing-arrow (#8347) (Mordy Tikotzky)
+* 735d02d5 Update: Deprecate sourceCode.getComments() (fixes #8408) (#8434) (Kai Cataldo)
+* ac39e3b0 Update: no-unexpected-multiline to flag confusing division (fixes #8469) (#8475) (Teddy Katz)
+* e35107f0 Fix: indent crash on arrow functions without parens at start of line (#8477) (Teddy Katz)
+* 973adeb6 Docs: State that functions option only applies in ES2017 (fixes #7809) (#8468) (Thenaesh Elango)
+* 7bc6fe0a New: array-bracket-newline rule (#8314) (Jan Peer Stöcklmair)
+* 10a1a2d7 Chore: Do not use cache when testing (#8464) (Kai Cataldo)
+* 9f540fd2 Update: no-unused-vars false negative about destructuring (fixes #8442) (#8459) (Toru Nagashima)
+* 741ed393 Docs: Clarify how to run local ESLint installation (#8463) (Kai Cataldo)
+* fac53890 Breaking: Remove array-callback-return from recommended (fixes #8428) (#8433) (Kai Cataldo)
+* 288c96c1 Upgrade: dependencies (#8304) (alberto)
+* 48700fc8 Docs: Remove extra header line from LICENSE (#8448) (Teddy Katz)
+* 161ee4ea Chore: avoid cloning comments array in TokenStore (#8436) (Teddy Katz)
+* 0c2a386e Docs: clarify new indent behavior with MemberExpressions (#8432) (Teddy Katz)
+* 446b8876 Docs: update space-before-function-paren docs for 4.0 (fixes #8430) (#8431) (Teddy Katz)
+
 v4.0.0-alpha.0 - April 7, 2017
 
 * 950874f Docs: add 4.0.0 migration guide (fixes #8306) (#8313) (Teddy Katz)
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index ceae032ef..26c978f7c 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -263,7 +263,9 @@ Once you have an instance of `SourceCode`, you can use the methods on it to work
 
 * `getText(node)` - returns the source code for the given node. Omit `node` to get the whole source.
 * `getAllComments()` - returns an array of all comments in the source.
-* `getComments(node)` - returns the leading and trailing comments arrays for the given node.
+* `getCommentsBefore(nodeOrToken)` - returns an array of comment tokens that occur directly before the given node or token.
+* `getCommentsAfter(nodeOrToken)` - returns an array of comment tokens that occur directly after the given node or token.
+* `getCommentsInside(node)` - returns an array of all comment tokens inside a given node.
 * `getJSDocComment(node)` - returns the JSDoc comment for a given node or `null` if there is none.
 * `isSpaceBetweenTokens(first, second)` - returns true if there is a whitespace character between the two tokens.
 * `getFirstToken(node, skipOptions)` - returns the first token representing the given node.
@@ -307,6 +309,14 @@ There are also some properties you can access:
 
 You should use a `SourceCode` object whenever you need to get more information about the code being linted.
 
+#### Deprecated
+
+Please note that the following methods have been deprecated and will be removed in a future version of ESLint:
+
+* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()`
+* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option
+* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option
+
 ### Options Schemas
 
 Rules may export a `schema` property, which is a [JSON schema](http://json-schema.org/) format description of a rule's options which will be used by ESLint to validate configuration options and prevent invalid or unexpected inputs before they are passed to the rule in `context.options`.
@@ -370,9 +380,9 @@ While comments are not technically part of the AST, ESLint provides a few ways f
 
 This method returns an array of all the comments found in the program. This is useful for rules that need to check all comments regardless of location.
 
-#### sourceCode.getComments(node)
+#### sourceCode.getCommentsBefore(), sourceCode.getCommentsAfter(), and sourceCode.getCommentsInside()
 
-This method returns comments for a specific node in the form of an object containing arrays of "leading" (occurring before the given node) and "trailing" (occurring after the given node) comment tokens. This is useful for rules that need to check comments around a given node.
+These methods return an array of comments that appear directly before, directly after, and inside nodes, respectively. They are useful for rules that need to check comments in relation to a given node or token.
 
 Keep in mind that the results of this method are calculated on demand.
 
diff --git a/docs/rules/comma-dangle.md b/docs/rules/comma-dangle.md
index 1897f6661..9eb3dedd3 100644
--- a/docs/rules/comma-dangle.md
+++ b/docs/rules/comma-dangle.md
@@ -68,8 +68,9 @@ The default for each option is `"never"` unless otherwise specified.
 * `objects` is for object literals and object patterns of destructuring. (e.g. `let {a,} = {a: 1};`)
 * `imports` is for import declarations of ES Modules. (e.g. `import {a,} from "foo";`)
 * `exports` is for export declarations of ES Modules. (e.g. `export {a,};`)
-* `functions` is for function declarations and function calls. (e.g. `(function(a,){ })(b,);`)<br>
-  `functions` is set to `"ignore"` by default for consistency with the string option.
+* `functions` is for function declarations and function calls. (e.g. `(function(a,){ })(b,);`)
+    * `functions` is set to `"ignore"` by default for consistency with the string option.
+    * `functions` should only be enabled when linting ECMAScript 2017 or higher.
 
 ### never
 
diff --git a/docs/rules/no-undefined.md b/docs/rules/no-undefined.md
index 19458c6ee..ff8bea720 100644
--- a/docs/rules/no-undefined.md
+++ b/docs/rules/no-undefined.md
@@ -1,6 +1,6 @@
 # Disallow Use of `undefined` Variable (no-undefined)
 
-The `undefined` variable is unique in JavaScript because it is actually a property of the global object. As such, in ECMAScript 3 it was possible to overwrite the value of `undefined`. While ECMAScript 5 disallows overwriting `undefined`, it's still possible to shadow `undefined`, such as:
+The `undefined` variable in JavaScript is actually a property of the global object. As such, in ECMAScript 3 it was possible to overwrite the value of `undefined`. While ECMAScript 5 disallows overwriting `undefined`, it's still possible to shadow `undefined`, such as:
 
 ```js
 function doSomething(data) {
@@ -14,24 +14,15 @@ function doSomething(data) {
 }
 ```
 
-This represents a problem for `undefined` that doesn't exist for `null`, which is a keyword and primitive value that can neither be overwritten nor shadowed.
+Because `undefined` can be overwritten or shadowed, reading `undefined` can give an unexpected value. (This is not the case for `null`, which is a keyword that always produces the same value.) To guard against this, you can avoid all uses of `undefined`, which is what some style guides recommend and what this rule enforces. Those style guides then also recommend:
 
-All uninitialized variables automatically get the value of `undefined`:
-
-```js
-var foo;
-
-console.log(foo === undefined);     // true (assuming no shadowing)
-```
-
-For this reason, it's not necessary to explicitly initialize a variable to `undefined`.
-
-Taking all of this into account, some style guides forbid the use of `undefined`, recommending instead:
-
-* Variables that should be `undefined` are simply left uninitialized.
+* Variables that should be `undefined` are simply left uninitialized. (All uninitialized variables automatically get the value of `undefined` in JavaScript.)
 * Checking if a value is `undefined` should be done with `typeof`.
 * Using the `void` operator to generate the value of `undefined` if necessary.
 
+As an alternative, you can use the [no-global-assign](no-global-assign.md) and [no-shadow-restricted-names](no-shadow-restricted-names.md) rules to prevent `undefined` from being shadowed or assigned a different value. This ensures that `undefined` will always hold its original, expected value.
+
+
 ## Rule Details
 
 This rule aims to eliminate the use of `undefined`, and as such, generates a warning whenever it is used.
@@ -84,3 +75,5 @@ If you want to allow the use of `undefined` in your code, then you can safely tu
 
 * [no-undef-init](no-undef-init.md)
 * [no-void](no-void.md)
+* [no-shadow-restricted-names](no-shadow-restricted-names.md)
+* [no-global-assign](no-global-assign.md)
diff --git a/docs/rules/no-unexpected-multiline.md b/docs/rules/no-unexpected-multiline.md
index 9fa9ca5fe..a328f83b2 100644
--- a/docs/rules/no-unexpected-multiline.md
+++ b/docs/rules/no-unexpected-multiline.md
@@ -32,6 +32,9 @@ let x = function() {}
 let x = function() {}
 x
 `hello`
+
+let x = foo
+/regex/g.test(bar)
 ```
 
 Examples of **correct** code for this rule:
diff --git a/docs/rules/object-curly-newline.md b/docs/rules/object-curly-newline.md
index a548f4eac..40f5368e5 100644
--- a/docs/rules/object-curly-newline.md
+++ b/docs/rules/object-curly-newline.md
@@ -16,7 +16,8 @@ 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
+* `"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`
+* `"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:
 
@@ -312,6 +313,86 @@ let {k = function() {
 }} = obj;
 ```
 
+### consistent
+
+Examples of **incorrect** code for this rule with the `{ "consistent": true }` option:
+
+```js
+/*eslint object-curly-newline: ["error", { "consistent": true }]*/
+/*eslint-env es6*/
+
+let a = {foo: 1
+};
+let b = {
+    foo: 1};
+let c = {foo: 1, bar: 2
+};
+let d = {
+    foo: 1, bar: 2};
+let e = {foo: 1,
+    bar: 2};
+let f = {foo: function() {
+    dosomething();
+}};
+
+let {g
+} = obj;
+let {
+    h} = obj;
+let {i, j
+} = obj;
+let {
+    k, l} = obj;
+let {m,
+    n} = obj;
+let {o = function() {
+    dosomething();
+}} = obj;
+```
+
+Examples of **correct** code for this rule with the `{ "consistent": true }` option:
+
+```js
+/*eslint object-curly-newline: ["error", { "consistent": true }]*/
+/*eslint-env es6*/
+
+let a = {};
+let b = {foo: 1};
+let c = {
+    foo: 1
+};
+let d = {
+    foo: 1, bar: 2
+};
+let e = {
+    foo: 1,
+    bar: 2
+};
+let f = {foo: function() {dosomething();}};
+let g = {
+    foo: function() {
+        dosomething();
+    }
+};
+
+let {} = obj;
+let {h} = obj;
+let {i, j} = obj;
+let {
+    k, l
+} = obj;
+let {
+    m,
+    n
+} = obj;
+let {o = function() {dosomething();}} = obj;
+let {
+    p = function() {
+        dosomething();
+    }
+} = obj;
+```
+
 ### ObjectExpression and ObjectPattern
 
 Examples of **incorrect** code for this rule with the `{ "ObjectExpression": "always", "ObjectPattern": "never" }` options:
diff --git a/docs/user-guide/migrating-to-4.0.0.md b/docs/user-guide/migrating-to-4.0.0.md
index e616dd56f..84accfea4 100644
--- a/docs/user-guide/migrating-to-4.0.0.md
+++ b/docs/user-guide/migrating-to-4.0.0.md
@@ -165,17 +165,45 @@ Starting in 4.0, the `RuleTester` utility will validate properties of test case
 
 Prior to 4.0, ESLint required parsers to implement comment attachment, a process where AST nodes would gain additional properties corresponding to their leading and trailing comments in the source file. This made it difficult for users to develop custom parsers, because they would have to replicate the confusing comment attachment semantics required by ESLint.
 
-In 4.0, comment attachment logic has been moved into ESLint itself. This should make it easier to develop custom parsers, but it also means that AST nodes will no longer have `leadingComments` and `trailingComments` properties. Additionally, `LineComment` and `BlockComment` events will no longer be emitted during AST traversal.
+In 4.0, we have moved away from the concept of comment attachment and have moved all comment handling logic into ESLint itself. This should make it easier to develop custom parsers, but it also means that AST nodes will no longer have `leadingComments` and `trailingComments` properties. Conceptually, rule authors can now think of comments in the context of tokens rather than AST nodes.
 
-**To address:** If you have a custom rule that depends on the `leadingComments` or `trailingComments` properties of an AST node, you can switch to `sourceCode.getComments(node).leading` or `sourceCode.getComments(node).trailing` instead. You should also consider using `sourceCode.getAllComments()` or `sourceCode.getTokenBefore(node, { includeComments: true })`.
+**To address:** If you have a custom rule that depends on the `leadingComments` or `trailingComments` properties of an AST node, you can now use `sourceCode.getCommentsBefore()` and `sourceCode.getCommentsAfter()` instead, respectively.
+
+Additionally, the `sourceCode` object now also has `sourceCode.getCommentsInside()` (which returns all the comments inside a node), `sourceCode.getAllComments()` (which returns all the comments in the file), and allows comments to be accessed through various other token iterator methods (such as `getTokenBefore()` and `getTokenAfter()`) with the `{ includeComments: true }` option.
+
+For rule authors concerned about supporting ESLint v3.0 in addition to v4.0, the now deprecated `sourceCode.getComments()` is still available and will work for both versions.
+
+Finally, please note that the following `SourceCode` methods have been deprecated and will be removed in a future version of ESLint:
+
+* `getComments()` - replaced by `getCommentsBefore()`, `getCommentsAfter()`, and `getCommentsInside()`
+* `getTokenOrCommentBefore()` - replaced by `getTokenBefore()` with the `{ includeComments: true }` option
+* `getTokenOrCommentAfter()` - replaced by `getTokenAfter()` with the `{ includeComments: true }` option
+
+## <a name="event-comments"/> `LineComment` and `BlockComment` events will no longer be emitted during AST traversal
+
+Starting in 4.0, `LineComment` and `BlockComments` events will not be emitted during AST traversal. There are two reasons for this:
+
+* This behavior was relying on comment attachment happening at the parser level, which does not happen anymore, to ensure that all comments would be accounted for
+* Thinking of comments in the context of tokens is more predictable and easier to reason about than thinking about comment tokens in the context of AST nodes
+
+**To address:** Instead of relying on `LineComment` and `BlockComment`, rules can now use `sourceCode.getAllComments()` to get all comments in a file. To check all comments of a specific type, rules can use the following pattern:
+
+```
+sourceCode.getAllComments().filter(comment => comment.type === "Line");
+sourceCode.getAllComments().filter(comment => comment.type === "Block");
+```
 
 ## <a name="shebangs"/> Shebangs are now returned from comment APIs
 
 Prior to 4.0, shebang comments in a source file would not appear in the output of `sourceCode.getAllComments()` or `sourceCode.getComments()`, but they would appear in the output of `sourceCode.getTokenOrCommentBefore` as line comments. This inconsistency led to some confusion for rule developers.
 
-In 4.0, shebang comments are included in the results of all of these methods. Instead of being converted to line comments, they will now have the `Shebang` type.
+In 4.0, shebang comments are treated as comment tokens of type `Shebang` and will be returned by any `SourceCode` method that returns comments. The goal of this change is to make working with shebang comments more consistent with how other tokens are handled.
 
-**To address:** If you have a custom rule that performs operations on comments, make sure to handle shebang comments appropriately.
+**To address:** If you have a custom rule that performs operations on comments, some additional logic might be required to ensure that shebang comments are correctly handled or filtered out:
+
+```
+sourceCode.getAllComments().filter(comment => comment.type !== "Shebang");
+```
 
 ## <a name="type-annotation-traversal"/> Type annotation nodes in an AST are now traversed
 
diff --git a/lib/ast-utils.js b/lib/ast-utils.js
index 85fa07200..98775f5ef 100644
--- a/lib/ast-utils.js
+++ b/lib/ast-utils.js
@@ -245,7 +245,7 @@ function hasJSDocThisTag(node, sourceCode) {
     // because callbacks don't have its JSDoc comment.
     // e.g.
     //     sinon.test(/* @this sinon.Sandbox */function() { this.spy(); });
-    return sourceCode.getComments(node).leading.some(comment => thisTagPattern.test(comment.value));
+    return sourceCode.getCommentsBefore(node).some(comment => thisTagPattern.test(comment.value));
 }
 
 /**
diff --git a/lib/eslint.js b/lib/eslint.js
index 9fcd6c43b..7671b7ccc 100755
--- a/lib/eslint.js
+++ b/lib/eslint.js
@@ -1030,6 +1030,9 @@ module.exports = (function() {
         getAllComments: "getAllComments",
         getNodeByRangeIndex: "getNodeByRangeIndex",
         getComments: "getComments",
+        getCommentsBefore: "getCommentsBefore",
+        getCommentsAfter: "getCommentsAfter",
+        getCommentsInside: "getCommentsInside",
         getJSDocComment: "getJSDocComment",
         getFirstToken: "getFirstToken",
         getFirstTokens: "getFirstTokens",
diff --git a/lib/rules/curly.js b/lib/rules/curly.js
index a9b8e94a4..e3cccc1f6 100644
--- a/lib/rules/curly.js
+++ b/lib/rules/curly.js
@@ -293,7 +293,7 @@ module.exports = {
                 }
             } else if (multiOrNest) {
                 if (hasBlock && body.body.length === 1 && isOneLiner(body.body[0])) {
-                    const leadingComments = sourceCode.getComments(body.body[0]).leading;
+                    const leadingComments = sourceCode.getCommentsBefore(body.body[0]);
 
                     expected = leadingComments.length > 0;
                 } else if (!isOneLiner(body)) {
diff --git a/lib/rules/default-case.js b/lib/rules/default-case.js
index 3efcbbced..32cd8dfe4 100644
--- a/lib/rules/default-case.js
+++ b/lib/rules/default-case.js
@@ -74,7 +74,7 @@ module.exports = {
                     let comment;
 
                     const lastCase = last(node.cases);
-                    const comments = sourceCode.getComments(lastCase).trailing;
+                    const comments = sourceCode.getCommentsAfter(lastCase);
 
                     if (comments.length) {
                         comment = last(comments);
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index a4258a070..a12e93330 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -698,6 +698,13 @@ module.exports = {
         */
         function addFunctionParamsIndent(node, paramsIndent) {
             const openingParen = node.params.length ? sourceCode.getTokenBefore(node.params[0]) : sourceCode.getTokenBefore(node.body, 1);
+
+            if (!openingParen) {
+
+                // If there is no opening paren (e.g. for an arrow function with a single parameter), don't indent anything.
+                return;
+            }
+
             const closingParen = sourceCode.getTokenBefore(node.body);
             const nodeTokens = getTokensAndComments(node);
             const openingParenIndex = lodash.sortedIndexBy(nodeTokens, openingParen, token => token.range[0]);
@@ -1030,7 +1037,7 @@ module.exports = {
                  * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly.
                  */
                 const precedingTokens = sourceCode.ast.comments.reduce((commentMap, comment) => {
-                    const tokenOrCommentBefore = sourceCode.getTokenOrCommentBefore(comment);
+                    const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { includeComments: true });
 
                     return commentMap.set(comment, commentMap.has(tokenOrCommentBefore) ? commentMap.get(tokenOrCommentBefore) : tokenOrCommentBefore);
                 }, new WeakMap());
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index 5f4a565fc..409fb451f 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -332,7 +332,7 @@ module.exports = {
             // Check that the first comment is adjacent to the end of the group, the
             // last comment is adjacent to the candidate property, and that successive
             // comments are adjacent to each other.
-            const leadingComments = sourceCode.getComments(candidate).leading;
+            const leadingComments = sourceCode.getCommentsBefore(candidate);
 
             if (
                 leadingComments.length &&
diff --git a/lib/rules/lines-around-directive.js b/lib/rules/lines-around-directive.js
index af2b3ca66..9899ae00a 100644
--- a/lib/rules/lines-around-directive.js
+++ b/lib/rules/lines-around-directive.js
@@ -131,7 +131,7 @@ module.exports = {
             }
 
             const firstDirective = directives[0];
-            const leadingComments = sourceCode.getComments(firstDirective).leading;
+            const leadingComments = sourceCode.getCommentsBefore(firstDirective);
 
             // Only check before the first directive if it is preceded by a comment or if it is at the top of
             // the file and expectLineBefore is set to "never". This is to not force a newline at the top of
diff --git a/lib/rules/newline-before-return.js b/lib/rules/newline-before-return.js
index 4dd0eae3d..05d35a09c 100644
--- a/lib/rules/newline-before-return.js
+++ b/lib/rules/newline-before-return.js
@@ -73,7 +73,7 @@ module.exports = {
          * @private
          */
         function calcCommentLines(node, lineNumTokenBefore) {
-            const comments = sourceCode.getComments(node).leading;
+            const comments = sourceCode.getCommentsBefore(node);
             let numLinesComments = 0;
 
             if (!comments.length) {
@@ -153,7 +153,7 @@ module.exports = {
          * @private
          */
         function canFix(node) {
-            const leadingComments = sourceCode.getComments(node).leading;
+            const leadingComments = sourceCode.getCommentsBefore(node);
             const lastLeadingComment = leadingComments[leadingComments.length - 1];
             const tokenBefore = sourceCode.getTokenBefore(node);
 
diff --git a/lib/rules/no-confusing-arrow.js b/lib/rules/no-confusing-arrow.js
index d6edbcc81..fc69ca39a 100644
--- a/lib/rules/no-confusing-arrow.js
+++ b/lib/rules/no-confusing-arrow.js
@@ -33,6 +33,8 @@ module.exports = {
             recommended: false
         },
 
+        fixable: "code",
+
         schema: [{
             type: "object",
             properties: {
@@ -55,7 +57,15 @@ module.exports = {
             const body = node.body;
 
             if (isConditional(body) && !(config.allowParens && astUtils.isParenthesised(sourceCode, body))) {
-                context.report({ node, message: "Arrow function used ambiguously with a conditional expression." });
+                context.report({
+                    node,
+                    message: "Arrow function used ambiguously with a conditional expression.",
+                    fix(fixer) {
+
+                        // if `allowParens` is not set to true dont bother wrapping in parens
+                        return config.allowParens && fixer.replaceText(node.body, `(${sourceCode.getText(node.body)})`);
+                    }
+                });
             }
         }
 
diff --git a/lib/rules/no-empty.js b/lib/rules/no-empty.js
index a9b0776c9..b71b8582a 100644
--- a/lib/rules/no-empty.js
+++ b/lib/rules/no-empty.js
@@ -59,7 +59,7 @@ module.exports = {
                 }
 
                 // any other block is only allowed to be empty, if it contains a comment
-                if (sourceCode.getComments(node).trailing.length > 0) {
+                if (sourceCode.getCommentsInside(node).length > 0) {
                     return;
                 }
 
diff --git a/lib/rules/no-fallthrough.js b/lib/rules/no-fallthrough.js
index 30d13da06..082e8431d 100644
--- a/lib/rules/no-fallthrough.js
+++ b/lib/rules/no-fallthrough.js
@@ -25,7 +25,7 @@ const DEFAULT_FALLTHROUGH_COMMENT = /falls?\s?through/i;
  */
 function hasFallthroughComment(node, context, fallthroughCommentPattern) {
     const sourceCode = context.getSourceCode();
-    const comment = lodash.last(sourceCode.getComments(node).leading);
+    const comment = lodash.last(sourceCode.getCommentsBefore(node));
 
     return Boolean(comment && fallthroughCommentPattern.test(comment.value));
 }
diff --git a/lib/rules/no-unexpected-multiline.js b/lib/rules/no-unexpected-multiline.js
index 6c15f5dd5..2c2ac2db3 100644
--- a/lib/rules/no-unexpected-multiline.js
+++ b/lib/rules/no-unexpected-multiline.js
@@ -30,6 +30,9 @@ module.exports = {
         const FUNCTION_MESSAGE = "Unexpected newline between function and ( of function call.";
         const PROPERTY_MESSAGE = "Unexpected newline between object and [ of property access.";
         const TAGGED_TEMPLATE_MESSAGE = "Unexpected newline between template tag and template literal.";
+        const DIVISION_MESSAGE = "Unexpected newline between numerator and division operator.";
+
+        const REGEX_FLAG_MATCHER = /^[gimuy]+$/;
 
         const sourceCode = context.getSourceCode();
 
@@ -75,6 +78,19 @@ module.exports = {
                     return;
                 }
                 checkForBreakAfter(node.callee, FUNCTION_MESSAGE);
+            },
+
+            "BinaryExpression[operator='/'] > BinaryExpression[operator='/']"(node) {
+                const secondSlash = sourceCode.getTokenAfter(node, token => token.value === "/");
+                const tokenAfterOperator = sourceCode.getTokenAfter(secondSlash);
+
+                if (
+                    tokenAfterOperator.type === "Identifier" &&
+                    REGEX_FLAG_MATCHER.test(tokenAfterOperator.value) &&
+                    secondSlash.range[1] === tokenAfterOperator.range[0]
+                ) {
+                    checkForBreakAfter(node.left, DIVISION_MESSAGE);
+                }
             }
         };
 
diff --git a/lib/rules/object-curly-newline.js b/lib/rules/object-curly-newline.js
index a4451154d..b78cb9cfc 100644
--- a/lib/rules/object-curly-newline.js
+++ b/lib/rules/object-curly-newline.js
@@ -30,6 +30,9 @@ const OPTION_VALUE = {
                 minProperties: {
                     type: "integer",
                     minimum: 0
+                },
+                consistent: {
+                    type: "boolean"
                 }
             },
             additionalProperties: false,
@@ -42,11 +45,12 @@ const OPTION_VALUE = {
  * Normalizes a given option value.
  *
  * @param {string|Object|undefined} value - An option value to parse.
- * @returns {{multiline: boolean, minProperties: number}} Normalized option object.
+ * @returns {{multiline: boolean, minProperties: number, consistent: boolean}} Normalized option object.
  */
 function normalizeOptionValue(value) {
     let multiline = false;
     let minProperties = Number.POSITIVE_INFINITY;
+    let consistent = false;
 
     if (value) {
         if (value === "always") {
@@ -56,12 +60,13 @@ function normalizeOptionValue(value) {
         } else {
             multiline = Boolean(value.multiline);
             minProperties = value.minProperties || Number.POSITIVE_INFINITY;
+            consistent = Boolean(value.consistent);
         }
     } else {
         multiline = true;
     }
 
-    return { multiline, minProperties };
+    return { multiline, minProperties, consistent };
 }
 
 /**
@@ -172,7 +177,14 @@ module.exports = {
                     });
                 }
             } else {
-                if (!astUtils.isTokenOnSameLine(openBrace, first)) {
+                const consistent = options.consistent;
+                const hasLineBreakBetweenOpenBraceAndFirst = !astUtils.isTokenOnSameLine(openBrace, first);
+                const hasLineBreakBetweenCloseBraceAndLast = !astUtils.isTokenOnSameLine(last, closeBrace);
+
+                if (
+                    (!consistent && hasLineBreakBetweenOpenBraceAndFirst) ||
+                    (consistent && hasLineBreakBetweenOpenBraceAndFirst && !hasLineBreakBetweenCloseBraceAndLast)
+                ) {
                     context.report({
                         message: "Unexpected line break after this opening brace.",
                         node,
@@ -185,7 +197,10 @@ module.exports = {
                         }
                     });
                 }
-                if (!astUtils.isTokenOnSameLine(last, closeBrace)) {
+                if (
+                    (!consistent && hasLineBreakBetweenCloseBraceAndLast) ||
+                    (consistent && !hasLineBreakBetweenOpenBraceAndFirst && hasLineBreakBetweenCloseBraceAndLast)
+                ) {
                     context.report({
                         message: "Unexpected line break before this closing brace.",
                         node,
diff --git a/lib/rules/sort-imports.js b/lib/rules/sort-imports.js
index 2b382545e..74db02ad3 100644
--- a/lib/rules/sort-imports.js
+++ b/lib/rules/sort-imports.js
@@ -149,7 +149,7 @@ module.exports = {
                             message: "Member '{{memberName}}' of the import declaration should be sorted alphabetically.",
                             data: { memberName: importSpecifiers[firstUnsortedIndex].local.name },
                             fix(fixer) {
-                                if (importSpecifiers.some(specifier => sourceCode.getComments(specifier).leading.length || sourceCode.getComments(specifier).trailing.length)) {
+                                if (importSpecifiers.some(specifier => sourceCode.getCommentsBefore(specifier).length || sourceCode.getCommentsAfter(specifier).length)) {
 
                                     // If there are comments in the ImportSpecifier list, don't rearrange the specifiers.
                                     return null;
diff --git a/lib/rules/template-tag-spacing.js b/lib/rules/template-tag-spacing.js
index 808fe4438..907c537ff 100755
--- a/lib/rules/template-tag-spacing.js
+++ b/lib/rules/template-tag-spacing.js
@@ -45,7 +45,7 @@ module.exports = {
                     loc: tagToken.loc.start,
                     message: "Unexpected space between template tag and template literal.",
                     fix(fixer) {
-                        const comments = sourceCode.getComments(node.quasi).leading;
+                        const comments = sourceCode.getCommentsBefore(node.quasi);
 
                         // Don't fix anything if there's a single line comment after the template tag
                         if (comments.some(comment => comment.type === "Line")) {
diff --git a/lib/token-store/index.js b/lib/token-store/index.js
index 1106028cf..86510bcb7 100644
--- a/lib/token-store/index.js
+++ b/lib/token-store/index.js
@@ -12,6 +12,7 @@ const assert = require("assert");
 const cursors = require("./cursors");
 const ForwardTokenCursor = require("./forward-token-cursor");
 const PaddedTokenCursor = require("./padded-token-cursor");
+const astUtils = require("../ast-utils");
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -172,6 +173,24 @@ function createCursorWithPadding(tokens, comments, indexMap, startLoc, endLoc, b
     return createCursorWithCount(cursors.forward, tokens, comments, indexMap, startLoc, endLoc, beforeCount);
 }
 
+/**
+ * Gets comment tokens that are adjacent to the current cursor position.
+ * @param {Cursor} cursor - A cursor instance.
+ * @returns {Array} An array of comment tokens adjacent to the current cursor position.
+ * @private
+ */
+function getAdjacentCommentTokensFromCursor(cursor) {
+    const tokens = [];
+    let currentToken = cursor.getOneToken();
+
+    while (currentToken && astUtils.isCommentToken(currentToken)) {
+        tokens.push(currentToken);
+        currentToken = cursor.getOneToken();
+    }
+
+    return tokens;
+}
+
 //------------------------------------------------------------------------------
 // Exports
 //------------------------------------------------------------------------------
@@ -540,4 +559,54 @@ module.exports = class TokenStore {
             padding
         ).getAllTokens();
     }
+
+    /**
+     * Gets all comment tokens directly before the given node or token.
+     * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens.
+     * @returns {Array} An array of comments in occurrence order.
+     */
+    getCommentsBefore(nodeOrToken) {
+        const cursor = createCursorWithCount(
+            cursors.backward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            -1,
+            nodeOrToken.range[0],
+            { includeComments: true }
+        );
+
+        return getAdjacentCommentTokensFromCursor(cursor).reverse();
+    }
+
+    /**
+     * Gets all comment tokens directly after the given node or token.
+     * @param {ASTNode|token} nodeOrToken The AST node or token to check for adjacent comment tokens.
+     * @returns {Array} An array of comments in occurrence order.
+     */
+    getCommentsAfter(nodeOrToken) {
+        const cursor = createCursorWithCount(
+            cursors.forward,
+            this[TOKENS],
+            this[COMMENTS],
+            this[INDEX_MAP],
+            nodeOrToken.range[1],
+            -1,
+            { includeComments: true }
+        );
+
+        return getAdjacentCommentTokensFromCursor(cursor);
+    }
+
+    /**
+     * Gets all comment tokens inside the given node.
+     * @param {ASTNode} node The AST node to get the comments for.
+     * @returns {Array} An array of comments in occurrence order.
+     */
+    getCommentsInside(node) {
+        return this.getTokens(node, {
+            includeComments: true,
+            filter: astUtils.isCommentToken
+        });
+    }
 };
diff --git a/lib/util/source-code.js b/lib/util/source-code.js
index 014713aeb..af01ff3fa 100644
--- a/lib/util/source-code.js
+++ b/lib/util/source-code.js
@@ -173,8 +173,8 @@ class SourceCode extends TokenStore {
         }
         this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1]));
 
-        // Store for comments found using getComments().
-        this._commentStore = new WeakMap();
+        // Cache for comments found using getComments().
+        this._commentCache = new WeakMap();
 
         // don't allow modification of this object
         Object.freeze(this);
@@ -229,8 +229,8 @@ class SourceCode extends TokenStore {
      * @public
      */
     getComments(node) {
-        if (this._commentStore.has(node)) {
-            return this._commentStore.get(node);
+        if (this._commentCache.has(node)) {
+            return this._commentCache.get(node);
         }
 
         const comments = {
@@ -273,10 +273,12 @@ class SourceCode extends TokenStore {
                 if (node.parent && (currentToken.start < node.parent.start)) {
                     break;
                 }
-                comments.leading.unshift(currentToken);
+                comments.leading.push(currentToken);
                 currentToken = this.getTokenBefore(currentToken, { includeComments: true });
             }
 
+            comments.leading.reverse();
+
             currentToken = this.getTokenAfter(node, { includeComments: true });
 
             while (currentToken && astUtils.isCommentToken(currentToken)) {
@@ -288,7 +290,7 @@ class SourceCode extends TokenStore {
             }
         }
 
-        this._commentStore.set(node, comments);
+        this._commentCache.set(node, comments);
         return comments;
     }
 
@@ -301,23 +303,23 @@ class SourceCode extends TokenStore {
      */
     getJSDocComment(node) {
         let parent = node.parent;
-        const leadingComments = this.getComments(node).leading;
+        const leadingComments = this.getCommentsBefore(node);
 
         switch (node.type) {
             case "ClassDeclaration":
             case "FunctionDeclaration":
                 if (looksLikeExport(parent)) {
-                    return findJSDocComment(this.getComments(parent).leading, parent.loc.start.line);
+                    return findJSDocComment(this.getCommentsBefore(parent), parent.loc.start.line);
                 }
                 return findJSDocComment(leadingComments, node.loc.start.line);
 
             case "ClassExpression":
-                return findJSDocComment(this.getComments(parent.parent).leading, parent.parent.loc.start.line);
+                return findJSDocComment(this.getCommentsBefore(parent.parent), parent.parent.loc.start.line);
 
             case "ArrowFunctionExpression":
             case "FunctionExpression":
                 if (parent.type !== "CallExpression" && parent.type !== "NewExpression") {
-                    let parentLeadingComments = this.getComments(parent).leading;
+                    let parentLeadingComments = this.getCommentsBefore(parent);
 
                     while (!parentLeadingComments.length && !/Function/.test(parent.type) && parent.type !== "MethodDefinition" && parent.type !== "Property") {
                         parent = parent.parent;
@@ -326,10 +328,10 @@ class SourceCode extends TokenStore {
                             break;
                         }
 
-                        parentLeadingComments = this.getComments(parent).leading;
+                        parentLeadingComments = this.getCommentsBefore(parent);
                     }
 
-                    return parent && (parent.type !== "FunctionDeclaration") ? findJSDocComment(parentLeadingComments, parent.loc.start.line) : null;
+                    return parent && parent.type !== "FunctionDeclaration" && parent.type !== "Program" ? findJSDocComment(parentLeadingComments, parent.loc.start.line) : null;
                 } else if (leadingComments.length) {
                     return findJSDocComment(leadingComments, node.loc.start.line);
                 }
diff --git a/package.json b/package.json
index c2e82e9a9..5aca5f60c 100644
--- a/package.json
+++ b/package.json
@@ -1,6 +1,6 @@
 {
   "name": "eslint",
-  "version": "4.0.0-alpha.0",
+  "version": "4.0.0-alpha.1",
   "author": "Nicholas C. Zakas <nicholas+npm@nczconsulting.com>",
   "description": "An AST-based pattern checker for JavaScript.",
   "bin": {


Test patch:
diff --git a/tests/lib/eslint.js b/tests/lib/eslint.js
index ac6c68113..399b55570 100644
--- a/tests/lib/eslint.js
+++ b/tests/lib/eslint.js
@@ -2620,19 +2620,11 @@ describe("eslint", () => {
 
             eslint.reset();
 
-            eslint.on("Program", node => {
-                assert.equal(node.comments.length, 1);
-                assert.equal(node.comments[0].type, "Shebang");
-
-                let comments = eslint.getComments(node);
-
-                assert.equal(comments.leading.length, 0);
-                assert.equal(comments.trailing.length, 0);
+            eslint.on("Program", () => {
+                const comments = eslint.getAllComments();
 
-                comments = eslint.getComments(node.body[0]);
-                assert.equal(comments.leading.length, 1);
-                assert.equal(comments.trailing.length, 0);
-                assert.equal(comments.leading[0].type, "Shebang");
+                assert.equal(comments.length, 1);
+                assert.equal(comments[0].type, "Shebang");
             });
             eslint.verify(code, config, "foo.js", true);
         });
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 4a926f427..e965c8aad 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -3041,6 +3041,10 @@ ruleTester.run("indent", rule, {
 
                 ; [1, 2, 3].map(baz)
             `
+        },
+        {
+            code: "x => {}",
+            parserOptions: { ecmaVersion: 6 }
         }
     ],
 
diff --git a/tests/lib/rules/no-confusing-arrow.js b/tests/lib/rules/no-confusing-arrow.js
index 7448b8a55..4efa39497 100644
--- a/tests/lib/rules/no-confusing-arrow.js
+++ b/tests/lib/rules/no-confusing-arrow.js
@@ -28,19 +28,41 @@ ruleTester.run("no-confusing-arrow", rule, {
     invalid: [
         {
             code: "a => 1 ? 2 : 3",
+            output: null,
             errors: [{ message: "Arrow function used ambiguously with a conditional expression." }]
         },
         {
             code: "var x = a => 1 ? 2 : 3",
+            output: null,
             errors: [{ message: "Arrow function used ambiguously with a conditional expression." }]
         },
         {
             code: "var x = (a) => 1 ? 2 : 3",
+            output: null,
             errors: [{ message: "Arrow function used ambiguously with a conditional expression." }]
         },
         {
             code: "var x = a => (1 ? 2 : 3)",
+            output: null,
             errors: [{ message: "Arrow function used ambiguously with a conditional expression." }]
+        },
+        {
+            code: "a => 1 ? 2 : 3",
+            output: "a => (1 ? 2 : 3)",
+            errors: [{ message: "Arrow function used ambiguously with a conditional expression." }],
+            options: [{ allowParens: true }]
+        },
+        {
+            code: "var x = a => 1 ? 2 : 3",
+            output: "var x = a => (1 ? 2 : 3)",
+            errors: [{ message: "Arrow function used ambiguously with a conditional expression." }],
+            options: [{ allowParens: true }]
+        },
+        {
+            code: "var x = (a) => 1 ? 2 : 3",
+            output: "var x = (a) => (1 ? 2 : 3)",
+            errors: [{ message: "Arrow function used ambiguously with a conditional expression." }],
+            options: [{ allowParens: true }]
         }
     ]
 });
diff --git a/tests/lib/rules/no-unexpected-multiline.js b/tests/lib/rules/no-unexpected-multiline.js
index 3a5dee1e2..a3b662b0d 100644
--- a/tests/lib/rules/no-unexpected-multiline.js
+++ b/tests/lib/rules/no-unexpected-multiline.js
@@ -42,7 +42,43 @@ ruleTester.run("no-unexpected-multiline", rule, {
         {
             code: "x\n.y\nz `Valid Test Case`",
             parserOptions: { ecmaVersion: 6 }
-        }
+        },
+        `
+            foo
+            / bar /2
+        `,
+        `
+            foo
+            / bar / mgy
+        `,
+        `
+            foo
+            / bar /
+            gym
+        `,
+        `
+            foo
+            / bar
+            / ygm
+        `,
+        `
+            foo
+            / bar /GYM
+        `,
+        `
+            foo
+            / bar / baz
+        `,
+        "foo /bar/g",
+        `
+            foo
+            /denominator/
+            2
+        `,
+        `
+            foo
+            / /abc/
+        `
     ],
     invalid: [
         {
@@ -120,6 +156,50 @@ ruleTester.run("no-unexpected-multiline", rule, {
                 column: 1,
                 message: "Unexpected newline between template tag and template literal."
             }]
+        },
+        {
+            code: `
+                foo
+                / bar /gym
+            `,
+            errors: [{
+                line: 3,
+                column: 17,
+                message: "Unexpected newline between numerator and division operator."
+            }]
+        },
+        {
+            code: `
+                foo
+                / bar /g
+            `,
+            errors: [{
+                line: 3,
+                column: 17,
+                message: "Unexpected newline between numerator and division operator."
+            }]
+        },
+        {
+            code: `
+                foo
+                / bar /g.test(baz)
+            `,
+            errors: [{
+                line: 3,
+                column: 17,
+                message: "Unexpected newline between numerator and division operator."
+            }]
+        },
+        {
+            code: `
+                foo
+                /bar/gimuygimuygimuy.test(baz)
+            `,
+            errors: [{
+                line: 3,
+                column: 17,
+                message: "Unexpected newline between numerator and division operator."
+            }]
         }
     ]
 });
diff --git a/tests/lib/rules/object-curly-newline.js b/tests/lib/rules/object-curly-newline.js
index 4d8859742..05376e0d4 100644
--- a/tests/lib/rules/object-curly-newline.js
+++ b/tests/lib/rules/object-curly-newline.js
@@ -236,6 +236,162 @@ ruleTester.run("object-curly-newline", rule, {
             options: [{ multiline: true, minProperties: 2 }]
         },
 
+        // "consistent" ------------------------------------------
+        {
+            code: [
+                "var b = {",
+                "    a: 1",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }]
+        },
+        {
+            code: [
+                "var c = {a: 1, b: 2};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }]
+        },
+        {
+            code: [
+                "var c = {",
+                "    a: 1,",
+                "    b: 2",
+                "};"
+            ].join("\n"),
+
+            options: [{ multiline: true, consistent: true }]
+        },
+        {
+            code: [
+                "var e = {a: function() { dosomething();}};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }]
+        },
+        {
+            code: [
+                "var e = {",
+                "    a: function() { dosomething();}",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }]
+        },
+        {
+            code: [
+                "let {} = {a: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {a} = {a: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {",
+                "} = {a: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {",
+                "    a",
+                "} = {a: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {a, b} = {a: 1, b: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {",
+                "    a, b",
+                "} = {a: 1, b: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {k = function() {dosomething();}} = obj;"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {",
+                "    k = function() {",
+                "        dosomething();",
+                "    }",
+                "} = obj;"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "var c = {a: 1,",
+                "b: 2};"
+            ].join("\n"),
+            options: [{ multiline: false, consistent: true }]
+        },
+        {
+            code: [
+                "let {a,",
+                "b} = {a: 1, b: 1};"
+            ].join("\n"),
+            options: [{ multiline: false, consistent: true }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+
+        // "consistent" and "minProperties" ------------------------------------------
+        {
+            code: [
+                "var c = { a: 1 };"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true, minProperties: 2 }]
+        },
+        {
+            code: [
+                "var c = {",
+                "a: 1",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true, minProperties: 2 }]
+        },
+        {
+            code: [
+                "let {a} = {",
+                "a: 1",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true, minProperties: 2 }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+        {
+            code: [
+                "let {",
+                "a",
+                "} = {",
+                "a: 1",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true, minProperties: 2 }],
+            parserOptions: { ecmaVersion: 6 }
+        },
+
         // "ObjectExpression" and "ObjectPattern" ---------------------------------------------
         {
             code: [
@@ -659,6 +815,289 @@ ruleTester.run("object-curly-newline", rule, {
             ]
         },
 
+        // "consistent" ------------------------------------------
+        {
+            code: [
+                "var b = {a: 1",
+                "};"
+            ].join("\n"),
+            output: [
+                "var b = {a: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            errors: [
+                { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var b = {",
+                "a: 1};"
+            ].join("\n"),
+            output: [
+                "var b = {a: 1};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "var c = {a: 1, b: 2",
+                "};"
+            ].join("\n"),
+            output: [
+                "var c = {a: 1, b: 2};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            errors: [
+                { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var c = {",
+                "a: 1, b: 2};"
+            ].join("\n"),
+            output: [
+                "var c = {a: 1, b: 2};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "var c = {a: 1,",
+                "b: 2};"
+            ].join("\n"),
+            output: [
+                "var c = {",
+                "a: 1,",
+                "b: 2",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." },
+                { line: 2, column: 5, message: "Expected a line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var e = {a: function() {",
+                "dosomething();",
+                "}};"
+            ].join("\n"),
+            output: [
+                "var e = {",
+                "a: function() {",
+                "dosomething();",
+                "}",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." },
+                { line: 3, column: 2, message: "Expected a line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "let {a",
+                "} = {a: 1}"
+            ].join("\n"),
+            output: [
+                "let {a} = {a: 1}"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "let {",
+                "a} = {a: 1}"
+            ].join("\n"),
+            output: [
+                "let {a} = {a: 1}"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 1, column: 5, message: "Unexpected line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "let {a, b",
+                "} = {a: 1, b: 2}"
+            ].join("\n"),
+            output: [
+                "let {a, b} = {a: 1, b: 2}"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 2, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "let {",
+                "a, b} = {a: 1, b: 2}"
+            ].join("\n"),
+            output: [
+                "let {a, b} = {a: 1, b: 2}"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 1, column: 5, message: "Unexpected line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "let {a,",
+                "b} = {a: 1, b: 2}"
+            ].join("\n"),
+            output: [
+                "let {",
+                "a,",
+                "b",
+                "} = {a: 1, b: 2}"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 1, column: 5, message: "Expected a line break after this opening brace." },
+                { line: 2, column: 2, message: "Expected a line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "let {e = function() {",
+                "dosomething();",
+                "}} = a;"
+            ].join("\n"),
+            output: [
+                "let {",
+                "e = function() {",
+                "dosomething();",
+                "}",
+                "} = a;"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 1, column: 5, message: "Expected a line break after this opening brace." },
+                { line: 3, column: 2, message: "Expected a line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "var c = {",
+                "a: 1,",
+                "b: 2};"
+            ].join("\n"),
+            output: [
+                "var c = {a: 1,",
+                "b: 2};"
+            ].join("\n"),
+            options: [{ multiline: false, consistent: true }],
+            errors: [
+                { line: 1, column: 9, message: "Unexpected line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "var c = {a: 1,",
+                "b: 2",
+                "};"
+            ].join("\n"),
+            output: [
+                "var c = {a: 1,",
+                "b: 2};"
+            ].join("\n"),
+            options: [{ multiline: false, consistent: true }],
+            errors: [
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "let {",
+                "a,",
+                "b} = {a: 1, b: 2};"
+            ].join("\n"),
+            output: [
+                "let {a,",
+                "b} = {a: 1, b: 2};"
+            ].join("\n"),
+            options: [{ multiline: false, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 1, column: 5, message: "Unexpected line break after this opening brace." }
+            ]
+        },
+        {
+            code: [
+                "let {a,",
+                "b",
+                "} = {a: 1, b: 2};"
+            ].join("\n"),
+            output: [
+                "let {a,",
+                "b} = {a: 1, b: 2};"
+            ].join("\n"),
+            options: [{ multiline: false, consistent: true }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 3, column: 1, message: "Unexpected line break before this closing brace." }
+            ]
+        },
+
+        // "consistent" and "minProperties" ------------------------------------------
+        {
+            code: [
+                "var c = {a: 1, b: 2};"
+            ].join("\n"),
+            output: [
+                "var c = {",
+                "a: 1, b: 2",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true, minProperties: 2 }],
+            errors: [
+                { line: 1, column: 9, message: "Expected a line break after this opening brace." },
+                { line: 1, column: 20, message: "Expected a line break before this closing brace." }
+            ]
+        },
+        {
+            code: [
+                "let {a, b} = {",
+                "a: 1, b: 2",
+                "};"
+            ].join("\n"),
+            output: [
+                "let {",
+                "a, b",
+                "} = {",
+                "a: 1, b: 2",
+                "};"
+            ].join("\n"),
+            options: [{ multiline: true, consistent: true, minProperties: 2 }],
+            parserOptions: { ecmaVersion: 6 },
+            errors: [
+                { line: 1, column: 5, message: "Expected a line break after this opening brace." },
+                { line: 1, column: 10, message: "Expected a line break before this closing brace." }
+            ]
+        },
+
         // "ObjectExpression" and "ObjectPattern" ---------------------------------------------
         {
             code: [
diff --git a/tests/lib/token-store.js b/tests/lib/token-store.js
index a465918f6..21c0c10c5 100644
--- a/tests/lib/token-store.js
+++ b/tests/lib/token-store.js
@@ -1272,4 +1272,116 @@ describe("TokenStore", () => {
             );
         });
     });
+
+    describe("getCommentsBefore", () => {
+        it("should retrieve comments before a node", () => {
+            assert.equal(
+                store.getCommentsBefore(VariableDeclaration)[0].value,
+                "A"
+            );
+        });
+
+        it("should retrieve comments before a token", () => {
+            assert.equal(
+                store.getCommentsBefore(TOKENS[2] /* "=" token */)[0].value,
+                "B"
+            );
+        });
+
+        it("should retrieve multiple comments before a node", () => {
+            const comments = store.getCommentsBefore(CallExpression);
+
+            assert.equal(comments.length, 2);
+            assert.equal(comments[0].value, "E");
+            assert.equal(comments[1].value, "F");
+        });
+
+        it("should retrieve comments before a Program node", () => {
+            assert.equal(
+                store.getCommentsBefore(Program)[0].value,
+                "A"
+            );
+        });
+
+        it("should return an empty array if there are no comments before a node or token", () => {
+            check(
+                store.getCommentsBefore(BinaryExpression.right),
+                []
+            );
+            check(
+                store.getCommentsBefore(TOKENS[1]),
+                []
+            );
+        });
+    });
+
+    describe("getCommentsAfter", () => {
+        it("should retrieve comments after a node", () => {
+            assert.equal(
+                store.getCommentsAfter(VariableDeclarator.id)[0].value,
+                "B"
+            );
+        });
+
+        it("should retrieve comments after a token", () => {
+            assert.equal(
+                store.getCommentsAfter(TOKENS[2] /* "=" token */)[0].value,
+                "C"
+            );
+        });
+
+        it("should retrieve multiple comments after a node", () => {
+            const comments = store.getCommentsAfter(VariableDeclaration);
+
+            assert.equal(comments.length, 2);
+            assert.equal(comments[0].value, "E");
+            assert.equal(comments[1].value, "F");
+        });
+
+        it("should retrieve comments after a Program node", () => {
+            assert.equal(
+                store.getCommentsAfter(Program)[0].value,
+                "Z"
+            );
+        });
+
+        it("should return an empty array if there are no comments after a node or token", () => {
+            check(
+                store.getCommentsAfter(CallExpression.callee),
+                []
+            );
+            check(
+                store.getCommentsAfter(TOKENS[0]),
+                []
+            );
+        });
+    });
+
+    describe("getCommentsInside", () => {
+        it("should retrieve comments inside a node", () => {
+            check(
+                store.getCommentsInside(Program),
+                ["B", "C", "D", "E", "F"]
+            );
+            check(
+                store.getCommentsInside(VariableDeclaration),
+                ["B", "C", "D"]
+            );
+            check(
+                store.getCommentsInside(VariableDeclarator),
+                ["B", "C", "D"]
+            );
+            check(
+                store.getCommentsInside(BinaryExpression),
+                ["D"]
+            );
+        });
+
+        it("should return an empty array if a node does not contain any comments", () => {
+            check(
+                store.getCommentsInside(TOKENS[2]),
+                []
+            );
+        });
+    });
 });