Repository: eslint/eslint
Pull request #7516 (2016-11-01 22:03:35)

Breaking: Calculate leading/trailing comments in core
**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:

<!--
    If the item you've checked above has a template, please paste the template questions below and answer them. (If this pull request is addressing an issue, you can just paste a link to the issue here instead.)
-->

<!--
    Please ensure your pull request is ready:

    - Read the pull request guide (http://eslint.org/docs/developer-guide/contributing/pull-requests)
    - Include tests for this change
    - Update documentation for this change (if appropriate)
-->

<!--
    The following is required for all pull requests:
-->

**What changes did you make? (Give an overview)**
This PR turns off comment attachment in Espree and moves comment getting logic into `sourceCode.getComments()`. This is a breaking change.

**Is there anything you'd like reviewers to focus on?**
Would love suggestions for how else we might be able to handle shebang comments.

As discussed on the [corresponding issue](https://github.com/eslint/eslint/issues/6724), we should discuss if we want to continue thinking about comments the same way now that we're not attaching at the parser level. This PR mimics the current attachment strategy in Espree as close as it can, though it's not possible (nor do I think we want it to be) exactly the same as it is in Espree, because there are some unpredictable edge cases and bugs in that.

The version in this PR should essentially work the same for our users and ecosystem (unless they rely on some of the really weird edge cases mentioned before).



eslintbot: Thanks for the pull request, @kaicataldo! 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).)
mention-bot: @kaicataldo, thanks for your PR! By analyzing the history of the files in this pull request, we identified @btmills, @nzakas and @mysticatea to be potential reviewers.
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
kaicataldo: Sure, sorry that wasn't clear. 

### Differences between `getComments()` and Espree's comment attachment
* `getComments()` iterates over token lists and will always stop when it encounters a non-comment token (starting from the first token and checking before and the last token and checking after). This is different from the current behavior, as Espree collects comments as it parses and then attaches when it finishes a node. This means that comments can be attached across tokens and leads to some unexpected behavior (see examples below).

* It doesn't attach nodes that exist outside the range of the node's parent, as it should then be attached to that parent node (I didn't notice any differences between Espree and this PR for this behavior).

### Examples:
```js
foo /*comment*/ || /*comment*/ bar
var foo /*comment*/ = /*comment*/ bar;
```
**Espree:** In both examples above, the Identifier`foo` has 1 trailing comment while `bar` has 2 leading comments.

**sourceCode.getComments():** In both examples above, the Identifier`foo` has 1 trailing comment and the Identifier`bar` has 1 leading comments.

```js
function foo(/*asdf*/) {}
function foo(/*asdf*/bar) {}
```
**Espree:** In the first example above, the BlockStatement has a leading comment. In the second, the Identifier `bar` has a leading comment and the BlockStatement doesn't have any.

**sourceCode.getComments():** The first example does not return any comments. I wasn't sure what the desired behavior would be here - should it attach the comment as a trailing comment when a function node's `params` is empty? In the second, the Identifier `bar` has a leading comment. The BlockStatement does have leading comments in either case.

### Questions/Concerns
* How should we handle shebang comments (since they shouldn't be included in the results for `getComments()`?
* How should an empty function node params comments be treated? This is where the model of leading/trailing comments starts making less sense. I don't think it makes sense for them to be attached to the function body as a leading comment.
* How does everyone feel about the slightly changed (and more predictable) behavior described above?


nzakas: Thanks, that's super helpful. I think the new behavior you've described makes a lot of sense, and have no objections to either dropping the comment without a node or the slightly changed attachment behavior of leading comments.
kaicataldo: Working on this has led me to have some questions around how we want to handle shebangs. Essentially, it seems like we actually probably want to keep the current behavior of not including shebangs in the AST's `comments` array and should not be included as a leading comment when we use `sourceCode.getComments()`. Please see [this comment thread](https://github.com/eslint/eslint/pull/7516#discussion_r86607115) for more context.

The problem as it currently stands is that `sourceCode.getComments()` doesn't have a way of knowing if a LineComment token was a shebang or not (since it gets transformed into a standard JS LineComment prior to parsing). I think we do want to keep the behavior of transforming the shebang and keeping it in the tokens and comment store, as this allows rules to use `sourceCode.getTokenOrCommentBefore()` to get the token that represents the shebang.

It seems like we have a few ways forward, and I wanted to see what you all thought:
* Right after parsing in lib/eslint.js, add a property (`shebang: true`?) to the LineComment token that represents the shebang and then not filter it out in `sourceCode.getComments()`'s return value.
* When `sourceCode` is instatiated, remove the shebang and store a property/add a helper method in `sourceCode` that allows access to the shebang token.
* Simply remove the shebang token and make rules figure this out for themselves (maybe by checking the first line of the source code). This is my least favorite option, because it feels like something we should be able to provide.

Thoughts? Suggestions? Things I missed?
platinumazure: I'd vote for option 2:

> * When sourceCode is instatiated, remove the shebang and store a property/add a helper method in sourceCode that allows access to the shebang token.

This is similar to how we handle the byte-order mark (BOM).

I would be okay with option 1 (add `shebang` property to the comment token itself). I would be opposed to option 3 (remove the comment entirely from SourceCode's store).
mikesherov: This sounds correct to me too:

> When sourceCode is instatiated, remove the shebang and store a property/add a helper method in sourceCode that allows access to the shebang token.
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
kaicataldo: Updated - thoughts on this approach? I thought about it some more and am actually uncomfortable with removing it from the token list, as this has potential to break rules that currently rely on shebangs being represented as line comment tokens. 

Treating the shebang like a line comment works for most cases - it's really only for cases that specifically need to know whether the line comment token it is checking represents a shebang that we don't cover at the moment. Adding the property to the token will continue to allow iterating over tokens (which seems the most likely way that rules will be checking this).

Another thought I had was changing the `type` of the comment token to `Shebang`. This could potentially break some rules that rely on checking for token `type === "Line", but would allow the token to not be removed.

Thanks for all the input!
eslintbot: Thanks for the pull request, @kaicataldo! 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).)
nzakas: I think that's fine. We should probably document the behavior somewhere, though, as this is subtle enough that I'm not sure anyone will be able to figure it out just be looking at the code.
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
kaicataldo: I believe I have addressed all the above comments. As I was writing out the documentation, I realized that it is a bit odd that we don't attach the shebang comment when calling `sourceCode.getComments()`, but that it's still included in the results of `sourceCode.getAllComments()` and `sourceCode.getTokenOrCommentBefore()`.

The current behavior (before this PR) is that `sourceCode.getTokenOrCommentBefore()` is the only way to access shebangs, because they are represented by "Line" comment tokens in `sourceCode.tokensAndComments`. `sourceCode.getComments()` and `sourceCode.getAllComments()` do not return the shebang, as the comment token is removed the from leading comments of the first child node in the body as well as the top level comments array.

Any thoughts on how we can make this clearer and improve the consistency? In both the current and proposed behavior in this PR, the inconsistency of whether a shebang constitutes a comment or not is confusing. 

## Some ideas: 
### Would it be better for shebangs to be accessed via a helper method through `sourceCode`?  

I'm not convinced that's helpful, though, since when you're checking for spacing around nodes you generally iterate over tokens. This could affect the `lines-around-comment` and `lines-around-directive` rules, which rely on `sourceCode.getTokenOrCommentBefore()` to ensure proper spacing even with shebangs, and could become an issue for a rule such as the one being discussed in [this proposal](https://github.com/eslint/eslint/issues/7356).

### Should we include it in all the methods that return comments?
Feels like it would be easier for the rule writer to to be able to access it as a comment token, but then I wonder if we include it in all the methods that return comments, for the sake of consistency and ease of use. This makes it very easy for whitespace-related rules, as they could then always use `sourceCode.getComments(node)`. Now that it has a unique type, it's easier to filter out if need be and may not be an issue (let me know if I'm missing something).
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
kaicataldo: @platinumazure That's right! Added the corresponding test. Thanks for reviewing this :)
eslintbot: Thanks for the pull request, @kaicataldo! 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).)
platinumazure: I'm honestly not sure what to do about the shebang. I like the idea of just treating it like any other comment (as you noted, the type is now unique so it could be filtered out if needed). But I don't feel qualified to answer, to be completely honest.
kaicataldo: @platinumazure Thanks for weighing in, regardless! I think treating it like any other comment would make things easier for rule writers - all most rules care about is the location of the shebang/comment so that they can enforce whether there is whitespace around them.

There might be some rules that will need to be modified (`spaced-comment` and `capitalized-comments` come to mind), but now that the shebang comment has the type `Shebang`, they would be easy to filter out. Though this would require a little more work on the part of the rule writer, this case seems to be a lot less common than rules that would only care about the shebang's location rather than its value.
not-an-aardvark: > but now that the shebang comment has the type Shebang, the shebang token would be easy to filter out.

This is only loosely related, but would it be possible to have a consistent flag or function to detect whether a given thing is a comment? When getting tokens/comments from `sourceCode.tokensAndComments`, it's not immediately obvious how to distinguish a token from a comment. I had previously been using `if (thing.type === "Line" || thing.type === "Block")` to detect comments, but it looks like that won't work anymore if we add a new `Shebang` type.
kaicataldo: @not-an-aardvark Yeah, definitely possible to make a helper method. The check up to this point is exactly as you described - to keep the same behavior with this PR it would just be `token.type === "Line" || token.type === "Block" || token.type === "Shebang"`.
nzakas: :+1: for treating shebang comments like any other comments. It seems like the most straightforward and expected approach. The one thing we need to be careful of is using `sourceCode.getText()` on that comment, as I think it will return `//` instead of `#` at the beginning right now.
kaicataldo: @nzakas Looks like we're okay, as `sourceCode` is instantiated with the original text (we transform the first two characters of a shebang for the parsing only), and that's what's used in `sourceCode.getText()`. Thanks for pointing that out - I've included a test to assert this behavior.
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
kaicataldo: I need to give this one more look over with a fresh mind, but I believe I've addressed all the comments above. Shebangs are now treated as any other comments, including firing a `ShebangComment` event as traversal occurs.

@not-an-aardvark How do you feel about the addition of `isComment` to the `ast-utils`?

@eslint/eslint-team Wouldn't mind some more eyes on this to make sure I'm not missing anything/breaking something I shouldn't be :)
eslintbot: Thanks for the pull request, @kaicataldo! 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).)
not-an-aardvark: @not-an-aardvark How do you feel about the addition of isComment to the ast-utils?

Seems fine to me. I am slightly concerned that this function wouldn't be accessible to external rules -- ideally, I think it would be nice if external rules could have an authoritative "is this thing a comment" function that wouldn't break if we added more types of comments. But I think this is probably fine for now.
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
mikesherov: @kaicataldo, I was under the impression that this change wouldn't change anything about the emission of events so that using the LineComment or BlockComment visitors wouldn't change.

Is there some sort of guaranteed ordering of comment events (other than after Program:start and before Program:end) that precludes from just linearly iterating over all comments and emitting events for them right after Program:start? Or are there other technical reasons that isn't achievable that I'm unaware of?
kaicataldo: The [current implementation](https://github.com/eslint/eslint/blob/master/lib/util/comment-event-generator.js#L94) calls `sourceCode.getComments()` on each node while it traverses. I like the idea of iterating over the top level comments array, as this will ensure every comment is accounted for. From my understanding, this seems technically feasible, but I'm not sure if the order in which these events gets fired matters for this case or not. I'm inclined to think that it doesn't matter too much for comments.
nzakas: The intent with `LineComment` and `BlockComment` was to have these emitted inline with the traversal of the AST so you could, for example, create a rule that uses something like `/*eslint-disable*/` and `/*eslint-enable*/` over a particular area of the code. IMHO, that capability is important to maintain.
kaicataldo: Ah, got it. Will keep thinking about this then.
kaicataldo: @nzakas I think creating a rule that uses enable/disable comments could still be possible without caring about comment traversal order. One could use `sourceCode.getAllComments()` to get all the comments, iterate over the array and find the ranges between the comments. It could then check that whatever node/token it's checking falls within that range or not and ignore it. 

This could allow us to do what @mikesherov suggested, which ensures that all comments fire an event, which I think is really important. I'm not sure how else we can ensure that we traverse through all comments, since they can anywhere. Espree got around this by always attaching comments, even if they were attached to the wrong node.

Thoughts?
nzakas: @kaicatalod I don't see how we can change `LineComment` and `BlockComment` to fire outside of the order they currently do. That would be a significant breaking change and I'm not sure there's a good enough reason to do that. There's nothing stopping people from manually grabbing `Program.comments` if they want to do a straight iteration, so I'd rather keep the events firing in the same order they do now.
mikesherov: Yeah, if order there is important, we definitely need a different way. Wasn't considering the case @nzakas had suggested.
platinumazure: Is the "Note:" in [this section of our rules guide](http://eslint.org/docs/developer-guide/working-with-rules#accessing-comments) a sufficient CYA to just deprecate and remove support for emitting those events, maybe doing a graceful deprecation over a couple of major releases?
kaicataldo: @nzakas Alright. The problem we need to solve then is how we ensure that all comments are visited (rules such as https://github.com/eslint/eslint/blob/master/lib/rules/spaced-comment.js rely on this).    We can approximate it in the `getComments()` algorithm for more common comment locations, but since they can occur anywhere (including in between tokens of a given node) it's hard to get around the lossy nature of doing this post-parse.

One potential solution could be to keep a tally of which comments have been visited using `getComments()` and then emit events for any remaining missed comments at the very end, but that kind of defeats the purpose (since they're still not being emitted in the proper order).
nzakas: Another possible solution is just to remove LineComment and BlockComment as events altogether and force people to figure out their own way of working with comments. My biggest concern is about changing the behavior of something that already exists, but removing it eliminates confusion and still lets people do what they want. 
kaicataldo: I can get behind that (though I still do like @mikesherov's suggestion, as I think it provides a more intuitive API when dealing with comment-specific rules). 

I don't want to derail this discussion, but if we're considering removing emitting comment events altogether and are also considering adding APIs to access comments during token traversal (https://github.com/eslint/eslint/issues/7810), I question whether we need `sourceCode.getComments()` at all. Seems redundant (and confusing, because you have to understand the algorithm) - would it not be more intuitive to simply call `sourceCode.getTokenOrComment(Before|After)()`?
not-an-aardvark: If it's useful as a datapoint, I never use `sourceCode.getComments(node)` because I've always found the `leading`/`trailing` paradigm unintuitive. I always use either `sourceCode.getTokenOrComment(Before|After)` or `sourceCode.ast.comments`.
nzakas: > I question whether we need sourceCode.getComments() at all. 

Maybe not, but I don't think we can remove it at this point, that just changes too much at once. We can deprecate it and maybe add getCommentBefore\After to provide the same functionality (getTokenOrComment isn't quite correct as you'll always need to check if the returned object is a comment or not). Then we can remove it at a later time. 
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
kaicataldo: I've updated the PR to reflect the discussion above (no longer emitting events for comments). I had to update all the rules that rely on this behavior, but was able to get them all working. Thoughts?
eslintbot: Thanks for the pull request, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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, @kaicataldo! 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).)
kaicataldo: ### As requested, here's a summary of the scope of changes contained in this PR:

#### Calculating leading/trailing comments
The first thing this PR does is turn off comment attachment in Espree, instead implementing its own token traversal algorithm to calculate leading and trailing comments around a given node. It was not possible to emulate Espree's (buggy) behavior.

#### Treating Shebangs as any other comment token
Shebangs are now comment tokens with type `Shebang`. Shebangs are currently only accessible through what feel like arbitrary/unrelated methods at the moment, and treating them like any other comment will hopefully make it both clearer how rule authors can access/account for them as well as make it easier to work with them.

#### Removing the firing of comment events during traversal
The current comment event firing logic uses `sourceCode.getComments()` to attach comments on nodes and then emit events for them. Due to the now lossy nature of comment attachment (Espree did not lose comments because it would collect all comments as it parsed and would do its best to attach them to the corresponding node. While this had some weird edge cases and was pretty buggy, it did ensure that all comments were accounted for), we are no longer guaranteed to emit events for all comments. This is detrimental for rules like [lines-around-comment](http://eslint.org/docs/rules/lines-around-comment), as they check all comments.

As you can see from the discussion above, we weighed a few alternative ways of emitting events for all comments. We ended up deciding that it was better to remove this behavior rather than keep it and change the order in which events were fired. 

From what I've seen, most use cases for accessing comment tokens falls into one of two categories:
 1. Checking for comments around specific nodes 
 1. Checking all comments in the program for a property/style

I've added to [the docs](https://github.com/eslint/eslint/pull/7516/files#diff-31ef8f31971065bcc300804ca4d9661fR346) explaining how to do the equivalent using `sourceCode` methods.

This PR in combination with the [TokenStore rewrite](https://github.com/eslint/eslint/pull/7936) will give rule authors a lot more control over dealing with comment tokens. I also like that that we think of comments in the context of token traversal rather than AST traversal!

@eslint/eslint-team Please let me know if anything isn't clear! I'm feeling pretty good about where this stands right now, but always happy to discuss/improve it!
eslintbot: Thanks for the pull request, @kaicataldo! 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).)
nzakas: That's a great summary, thanks! I'm pretty happy with where this PR ended up and really appreciate all the work you put into it. Unless others have objections, I think we should start planning the next major release to get this in (so the code doesn't get stale sitting here). 
kaicataldo: Great! The last thing I'd like to do with this PR is rebase it off of https://github.com/eslint/eslint/pull/7936 when it lands.
eslintbot: LGTM
eslintbot: LGTM
kaicataldo: @mysticatea Sorry to ping you about this, but do you have any idea what I might be doing wrong? 

The problem seems to occur when a switch statement is directly followed by another comment. Example:
```js
function a() {
    switch (a) {
        case b:
            return c;
        // no default
    }
    // some other comment
}
```

[As `sourceCode.getComment()` is iterating over the tokens](https://github.com/eslint/eslint/pull/7516/files#diff-4625bd821401815c773e90e5cecdcd73R269), it seems to skip the closing brace. So the returned tokens are:
```js
[{ 
    type: 'Line',
    value: ' no default'
}, { 
    type: 'Line',
    value: ' some other comment'
}]
```

I looked at this both last night and tonight and can't seem to figure out what I'm doing wrong. Any insight would be appreciated!
mysticatea: @kaicataldo Yes, it's https://github.com/eslint/eslint/pull/8055
kaicataldo: @mysticatea Aha! I thought I was going crazy, haha. Thanks!
eslintbot: LGTM
eslintbot: LGTM
kaicataldo: Tests should pass and we should be ready to go after https://github.com/eslint/eslint/pull/8055 lands and I can rebase off of master.
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
kaicataldo: This is now up to date and ready for review
kaicataldo: > Seeing the number of rules that skip shebang comments leads me to believe that the default should be to exclude, but that's just my two cents.

Yeah, as I was writing all those filters I had the thought that perhaps it would be nice to specify what types of comments `sourceCode.getAllComments()` returns (or, as you mentioned, excluding a shebang if it's present). Maybe we could add an opt-in option for when the shebang comment is desired (since I think that's the rare case)? Something like `sourceCode.getAllComments({ includeShebang: true })`?

>  I wonder if we can preserve visitor pattern for LineComment and BlockComment, I was unsure why those were removed.

The reason we removed visiting comments was that it was brought it up that changing the existing behavior could lead to more confusion than having to think about comments totally from a token iteration perspective ([comment here](https://github.com/eslint/eslint/pull/7516#issuecomment-272493694)).

> Seems like we can iterate over comments right before program:exit and preserve existing functionality.

I do think this is a viable alternative, but it would change the current behavior (since the comment events currently fire before and after the node to which they are attached). Though it would work, I'm not sure what firing all the events at once (unattached) buys a rule author over just calling `sourceCode.getAllComments()` and always thinking of comments from the perspective of token iteration rather than AST traversal.
mikesherov: > @mikesherov Thanks for the review!

Seeing the number of rules that skip shebang comments leads me to believe that the default should be to exclude, but that's just my two cents.
Yeah, as I was writing all those filters I had the thought that perhaps it would be nice to specify what types of comments sourceCode.getAllComments() returns (or, as you mentioned, excluding a shebang if it's present). Maybe we could add an opt-in option for when the shebang comment is desired (since I think that's the rarer case)? Something like sourceCode.getAllComments({ includeShebang: true })?

I wonder if we can preserve visitor pattern for LineComment and BlockComment, I was unsure why those were removed.
The reason we removed visiting comments was that it was brought it up that changing the existing behavior could lead to more confusion than having to think about comments totally from a token iteration perspective (comment here).

Seems like we can iterate over comments right before program:exit and preserve existing functionality.
I do think this is a viable alternative, but it would change the current behavior (since the comment events currently fire before and after the node to which they are attached). Though it would work, I'm not sure what firing all the events at once (unattached) buys a rule author over just calling sourceCode.getAllComments() and always thinking of comments from the perspective of token iteration rather than AST traversal. Curious what you think the benefits are!

Sounds good. Don't want to reopen that can of worms!
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
kaicataldo: @btmills Really, really good call - there's definitely a bug here. Obvious now, but the logic of checking for "empty" nodes with comments isn't smart enough, which is why the leading comments aren't attached (it only attaches the trailing "inner" comments). Will update this this weekend!
not-an-aardvark: Is the goal to make that all comments are attached somewhere? It might be possible to use a fuzzer to test that.
kaicataldo: @not-an-aardvark No, after iterating on this for a while [we decided that it would be okay to lose some comments](https://github.com/eslint/eslint/pull/7516#issuecomment-259522555). 

I honestly think it makes more sense to think of comments in the context of token lists than in the context of AST nodes, and I think I'm actually leaning towards generally recommending using token iterator methods over sourceCode.getComments(). @nzakas mentioned [possibly deprecating it](https://github.com/eslint/eslint/pull/7516#issuecomment-272639595), though I think we can just recommend using the new token iterator methods instead of a `source.getCommentBefore|After`.

Part of my goal in all this is to unify and codify how we deal with comments so that it's simple and predictable. I'm not against updating things afterwards as long as we ensure it doesn't get overly complex (or there are too many different ways to do the same thing or slightly different things).

All that to say that I'm okay with not account for all comments with `sourceCode.getComments()`, because we currently have the leading/trailing paradigm. If we recommend `sourceCode.getAllComments()` as a way to get all comments and token iterator methods to get comments contextually/individually, I think we have a simple platform to build on in the future, and updating `sourceCode.getComments(node)` to simply return all comments inside the range of a node could be useful in a future major version.
eslintbot: LGTM
eslintbot: LGTM
kaicataldo: @not-an-aardvark @btmills Thanks again for the thorough reviews! I have addressed all the comments (either with code changes or comments of my own). Please let me know what you think! Changes were made in the last two commits, so hopefully it's not too hard to re-review.
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM
kaicataldo: Also, rebased and ran eslint-canary against this branch - not seeing any new unexpected errors!
eslintbot: LGTM
not-an-aardvark: Is the CLA bot down? It's still waiting for the status to be reported (and it hasn't left a comment)
vitorbal: @not-an-aardvark that happened to me a couple of days ago. I had to force push to trigger the bot again.
eslintbot: LGTM
eslintbot: LGTM
eslintbot: LGTM

Patch:
diff --git a/docs/developer-guide/working-with-rules.md b/docs/developer-guide/working-with-rules.md
index a5d391fcd..ceae032ef 100644
--- a/docs/developer-guide/working-with-rules.md
+++ b/docs/developer-guide/working-with-rules.md
@@ -362,19 +362,27 @@ var nodeSourceWithFollowing = sourceCode.getText(node, 0, 2);
 
 In this way, you can look for patterns in the JavaScript text itself when the AST isn't providing the appropriate data (such as location of commas, semicolons, parentheses, etc.).
 
-### Accessing comments
+### Accessing Comments
 
-If you need to access comments for a specific node you can use `sourceCode.getComments(node)`:
+While comments are not technically part of the AST, ESLint provides a few ways for rules to access them:
 
-```js
-// the "comments" variable has a "leading" and "trailing" property containing
-// its leading and trailing comments, respectively
-var comments = sourceCode.getComments(node);
-```
+#### sourceCode.getAllComments()
+
+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)
+
+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.
+
+Keep in mind that the results of this method are calculated on demand.
+
+#### Token traversal methods
+
+Finally, comments can be accessed through many of `sourceCode`'s methods using the `includeComments` option.
 
-Keep in mind that comments are technically not a part of the AST and are only attached to it on demand, i.e. when you call `getComments()`.
+### Accessing Shebangs
 
-**Note:** One of the libraries adds AST node properties for comments - do not use these properties. Always use `sourceCode.getComments()` as this is the only guaranteed API for accessing comments (we will likely change how comments are handled later).
+Shebangs are represented by tokens of type `"Shebang"`. They are treated as comments and can be accessed by the methods outlined above.
 
 ### Accessing Code Paths
 
diff --git a/lib/ast-utils.js b/lib/ast-utils.js
index 978f19ad2..cfe11e500 100644
--- a/lib/ast-utils.js
+++ b/lib/ast-utils.js
@@ -27,6 +27,7 @@ const thisTagPattern = /^[\s*]*@this/m;
 const COMMENTS_IGNORE_PATTERN = /^\s*(?:eslint|jshint\s+|jslint\s+|istanbul\s+|globals?\s+|exported\s+|jscs)/;
 const LINEBREAKS = new Set(["\r\n", "\r", "\n", "\u2028", "\u2029"]);
 const LINEBREAK_MATCHER = /\r\n|[\r\n\u2028\u2029]/;
+const SHEBANG_MATCHER = /^#!([^\r\n]+)/;
 
 // A set of node types that can contain a list of statements
 const STATEMENT_LIST_PARENTS = new Set(["Program", "BlockStatement", "SwitchCase"]);
@@ -412,6 +413,7 @@ module.exports = {
     COMMENTS_IGNORE_PATTERN,
     LINEBREAKS,
     LINEBREAK_MATCHER,
+    SHEBANG_MATCHER,
     STATEMENT_LIST_PARENTS,
 
     /**
@@ -524,7 +526,7 @@ module.exports = {
 
     /**
      * Returns whether the provided node is an ESLint directive comment or not
-     * @param {LineComment|BlockComment} node The node to be checked
+     * @param {Line|Block} node The comment token to be checked
      * @returns {boolean} `true` if the node is an ESLint directive comment
      */
     isDirectiveComment(node) {
diff --git a/lib/eslint.js b/lib/eslint.js
index 070d36099..9fcd6c43b 100755
--- a/lib/eslint.js
+++ b/lib/eslint.js
@@ -20,13 +20,13 @@ const assert = require("assert"),
     ConfigOps = require("./config/config-ops"),
     validator = require("./config/config-validator"),
     Environments = require("./config/environments"),
-    CommentEventGenerator = require("./util/comment-event-generator"),
     NodeEventGenerator = require("./util/node-event-generator"),
     SourceCode = require("./util/source-code"),
     Traverser = require("./util/traverser"),
     RuleContext = require("./rule-context"),
     rules = require("./rules"),
     timing = require("./timing"),
+    astUtils = require("./ast-utils"),
 
     pkg = require("../package.json");
 
@@ -626,7 +626,6 @@ module.exports = (function() {
                 raw: true,
                 tokens: true,
                 comment: true,
-                attachComment: true,
                 filePath
             };
 
@@ -759,7 +758,6 @@ module.exports = (function() {
         const text = (typeof textOrSourceCode === "string") ? textOrSourceCode : null;
         let ast,
             parseResult,
-            shebang,
             allowInlineConfig;
 
         // evaluate arguments
@@ -801,10 +799,7 @@ module.exports = (function() {
             }
 
             parseResult = parse(
-                stripUnicodeBOM(text).replace(/^#!([^\r\n]+)/, (match, captured) => {
-                    shebang = captured;
-                    return `//${captured}`;
-                }),
+                stripUnicodeBOM(text).replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`),
                 config,
                 currentFilename
             );
@@ -903,19 +898,9 @@ module.exports = (function() {
             // augment global scope with declared global variables
             addDeclaredGlobals(ast, currentScopes[0], currentConfig);
 
-            // remove shebang comments
-            if (shebang && ast.comments.length && ast.comments[0].value === shebang) {
-                ast.comments.splice(0, 1);
-
-                if (ast.body.length && ast.body[0].leadingComments && ast.body[0].leadingComments[0].value === shebang) {
-                    ast.body[0].leadingComments.splice(0, 1);
-                }
-            }
-
             let eventGenerator = new NodeEventGenerator(api);
 
             eventGenerator = new CodePathAnalyzer(eventGenerator);
-            eventGenerator = new CommentEventGenerator(eventGenerator, sourceCode);
 
             /*
              * Each node has a type property. Whenever a particular type of
diff --git a/lib/rules/capitalized-comments.js b/lib/rules/capitalized-comments.js
index be2cf932a..bb5e5825a 100644
--- a/lib/rules/capitalized-comments.js
+++ b/lib/rules/capitalized-comments.js
@@ -295,7 +295,7 @@ module.exports = {
             Program() {
                 const comments = sourceCode.getAllComments();
 
-                comments.forEach(processComment);
+                comments.filter(token => token.type !== "Shebang").forEach(processComment);
             }
         };
     }
diff --git a/lib/rules/key-spacing.js b/lib/rules/key-spacing.js
index ce150753b..5f4a565fc 100644
--- a/lib/rules/key-spacing.js
+++ b/lib/rules/key-spacing.js
@@ -33,41 +33,6 @@ function last(arr) {
     return arr[arr.length - 1];
 }
 
-/**
- * Checks whether a property is a member of the property group it follows.
- * @param {ASTNode} lastMember The last Property known to be in the group.
- * @param {ASTNode} candidate The next Property that might be in the group.
- * @returns {boolean} True if the candidate property is part of the group.
- */
-function continuesPropertyGroup(lastMember, candidate) {
-    const groupEndLine = lastMember.loc.start.line,
-        candidateStartLine = candidate.loc.start.line;
-
-    if (candidateStartLine - groupEndLine <= 1) {
-        return true;
-    }
-
-    // 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 comments = candidate.leadingComments;
-
-    if (
-        comments &&
-        comments[0].loc.start.line - groupEndLine <= 1 &&
-        candidateStartLine - last(comments).loc.end.line <= 1
-    ) {
-        for (let i = 1; i < comments.length; i++) {
-            if (comments[i].loc.start.line - comments[i - 1].loc.end.line > 1) {
-                return false;
-            }
-        }
-        return true;
-    }
-
-    return false;
-}
-
 /**
  * Checks whether a node is contained on a single line.
  * @param {ASTNode} node AST Node being evaluated.
@@ -350,6 +315,41 @@ module.exports = {
 
         const sourceCode = context.getSourceCode();
 
+        /**
+         * Checks whether a property is a member of the property group it follows.
+         * @param {ASTNode} lastMember The last Property known to be in the group.
+         * @param {ASTNode} candidate The next Property that might be in the group.
+         * @returns {boolean} True if the candidate property is part of the group.
+         */
+        function continuesPropertyGroup(lastMember, candidate) {
+            const groupEndLine = lastMember.loc.start.line,
+                candidateStartLine = candidate.loc.start.line;
+
+            if (candidateStartLine - groupEndLine <= 1) {
+                return true;
+            }
+
+            // 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;
+
+            if (
+                leadingComments.length &&
+                leadingComments[0].loc.start.line - groupEndLine <= 1 &&
+                candidateStartLine - last(leadingComments).loc.end.line <= 1
+            ) {
+                for (let i = 1; i < leadingComments.length; i++) {
+                    if (leadingComments[i].loc.start.line - leadingComments[i - 1].loc.end.line > 1) {
+                        return false;
+                    }
+                }
+                return true;
+            }
+
+            return false;
+        }
+
         /**
          * Determines if the given property is key-value property.
          * @param {ASTNode} property Property node to check.
diff --git a/lib/rules/line-comment-position.js b/lib/rules/line-comment-position.js
index dd8f2b9ad..0df806cca 100644
--- a/lib/rules/line-comment-position.js
+++ b/lib/rules/line-comment-position.js
@@ -78,33 +78,37 @@ module.exports = {
         //--------------------------------------------------------------------------
 
         return {
-            LineComment(node) {
-                if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) {
-                    return;
-                }
-
-                if (ignorePattern && customIgnoreRegExp.test(node.value)) {
-                    return;
-                }
-
-                const previous = sourceCode.getTokenBefore(node, { includeComments: true });
-                const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
-
-                if (above) {
-                    if (isOnSameLine) {
-                        context.report({
-                            node,
-                            message: "Expected comment to be above code."
-                        });
+            Program() {
+                const comments = sourceCode.getAllComments();
+
+                comments.filter(token => token.type === "Line").forEach(node => {
+                    if (applyDefaultIgnorePatterns && (defaultIgnoreRegExp.test(node.value) || fallThroughRegExp.test(node.value))) {
+                        return;
+                    }
+
+                    if (ignorePattern && customIgnoreRegExp.test(node.value)) {
+                        return;
                     }
-                } else {
-                    if (!isOnSameLine) {
-                        context.report({
-                            node,
-                            message: "Expected comment to be beside code."
-                        });
+
+                    const previous = sourceCode.getTokenBefore(node, { includeComments: true });
+                    const isOnSameLine = previous && previous.loc.end.line === node.loc.start.line;
+
+                    if (above) {
+                        if (isOnSameLine) {
+                            context.report({
+                                node,
+                                message: "Expected comment to be above code."
+                            });
+                        }
+                    } else {
+                        if (!isOnSameLine) {
+                            context.report({
+                                node,
+                                message: "Expected comment to be beside code."
+                            });
+                        }
                     }
-                }
+                });
             }
         };
     }
diff --git a/lib/rules/lines-around-comment.js b/lib/rules/lines-around-comment.js
index e37dd8611..5b4cd8ebe 100644
--- a/lib/rules/lines-around-comment.js
+++ b/lib/rules/lines-around-comment.js
@@ -31,7 +31,7 @@ function getEmptyLineNums(lines) {
 
 /**
  * Return an array with with any line numbers that contain comments.
- * @param {Array} comments An array of comment nodes.
+ * @param {Array} comments An array of comment tokens.
  * @returns {Array} An array of line numbers.
  */
 function getCommentLineNums(comments) {
@@ -131,38 +131,28 @@ module.exports = {
             emptyLines = getEmptyLineNums(lines),
             commentAndEmptyLines = commentLines.concat(emptyLines);
 
-        /**
-         * Returns whether or not a token is a comment node type
-         * @param {Token} token The token to check
-         * @returns {boolean} True if the token is a comment node
-         */
-        function isCommentNodeType(token) {
-            return token && (token.type === "Block" || token.type === "Line");
-        }
-
         /**
          * Returns whether or not comments are on lines starting with or ending with code
-         * @param {ASTNode} node The comment node to check.
+         * @param {token} token The comment token to check.
          * @returns {boolean} True if the comment is not alone.
          */
-        function codeAroundComment(node) {
-            let token;
+        function codeAroundComment(token) {
+            let currentToken = token;
 
-            token = node;
             do {
-                token = sourceCode.getTokenBefore(token, { includeComments: true });
-            } while (isCommentNodeType(token));
+                currentToken = sourceCode.getTokenBefore(currentToken, { includeComments: true });
+            } while (currentToken && astUtils.isCommentToken(currentToken));
 
-            if (token && astUtils.isTokenOnSameLine(token, node)) {
+            if (currentToken && astUtils.isTokenOnSameLine(currentToken, token)) {
                 return true;
             }
 
-            token = node;
+            currentToken = token;
             do {
-                token = sourceCode.getTokenAfter(token, { includeComments: true });
-            } while (isCommentNodeType(token));
+                currentToken = sourceCode.getTokenAfter(currentToken, { includeComments: true });
+            } while (currentToken && astUtils.isCommentToken(currentToken));
 
-            if (token && astUtils.isTokenOnSameLine(node, token)) {
+            if (currentToken && astUtils.isTokenOnSameLine(token, currentToken)) {
                 return true;
             }
 
@@ -171,137 +161,135 @@ module.exports = {
 
         /**
          * Returns whether or not comments are inside a node type or not.
-         * @param {ASTNode} node The Comment node.
          * @param {ASTNode} parent The Comment parent node.
          * @param {string} nodeType The parent type to check against.
          * @returns {boolean} True if the comment is inside nodeType.
          */
-        function isCommentInsideNodeType(node, parent, nodeType) {
+        function isParentNodeType(parent, nodeType) {
             return parent.type === nodeType ||
                 (parent.body && parent.body.type === nodeType) ||
                 (parent.consequent && parent.consequent.type === nodeType);
         }
 
+        /**
+         * Returns the parent node that contains the given token.
+         * @param {token} token The token to check.
+         * @returns {ASTNode} The parent node that contains the given token.
+         */
+        function getParentNodeOfToken(token) {
+            return sourceCode.getNodeByRangeIndex(token.range[0]);
+        }
+
         /**
          * Returns whether or not comments are at the parent start or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @param {string} nodeType The parent type to check against.
          * @returns {boolean} True if the comment is at parent start.
          */
-        function isCommentAtParentStart(node, nodeType) {
-            const ancestors = context.getAncestors();
-            let parent;
+        function isCommentAtParentStart(token, nodeType) {
+            const parent = getParentNodeOfToken(token);
 
-            if (ancestors.length) {
-                parent = ancestors.pop();
-            }
-
-            return parent && isCommentInsideNodeType(node, parent, nodeType) &&
-                    node.loc.start.line - parent.loc.start.line === 1;
+            return parent && isParentNodeType(parent, nodeType) &&
+                    token.loc.start.line - parent.loc.start.line === 1;
         }
 
         /**
          * Returns whether or not comments are at the parent end or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @param {string} nodeType The parent type to check against.
          * @returns {boolean} True if the comment is at parent end.
          */
-        function isCommentAtParentEnd(node, nodeType) {
-            const ancestors = context.getAncestors();
-            let parent;
-
-            if (ancestors.length) {
-                parent = ancestors.pop();
-            }
+        function isCommentAtParentEnd(token, nodeType) {
+            const parent = getParentNodeOfToken(token);
 
-            return parent && isCommentInsideNodeType(node, parent, nodeType) &&
-                    parent.loc.end.line - node.loc.end.line === 1;
+            return parent && isParentNodeType(parent, nodeType) &&
+                    parent.loc.end.line - token.loc.end.line === 1;
         }
 
         /**
          * Returns whether or not comments are at the block start or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @returns {boolean} True if the comment is at block start.
          */
-        function isCommentAtBlockStart(node) {
-            return isCommentAtParentStart(node, "ClassBody") || isCommentAtParentStart(node, "BlockStatement") || isCommentAtParentStart(node, "SwitchCase");
+        function isCommentAtBlockStart(token) {
+            return isCommentAtParentStart(token, "ClassBody") || isCommentAtParentStart(token, "BlockStatement") || isCommentAtParentStart(token, "SwitchCase");
         }
 
         /**
          * Returns whether or not comments are at the block end or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @returns {boolean} True if the comment is at block end.
          */
-        function isCommentAtBlockEnd(node) {
-            return isCommentAtParentEnd(node, "ClassBody") || isCommentAtParentEnd(node, "BlockStatement") || isCommentAtParentEnd(node, "SwitchCase") || isCommentAtParentEnd(node, "SwitchStatement");
+        function isCommentAtBlockEnd(token) {
+            return isCommentAtParentEnd(token, "ClassBody") || isCommentAtParentEnd(token, "BlockStatement") || isCommentAtParentEnd(token, "SwitchCase") || isCommentAtParentEnd(token, "SwitchStatement");
         }
 
         /**
          * Returns whether or not comments are at the object start or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @returns {boolean} True if the comment is at object start.
          */
-        function isCommentAtObjectStart(node) {
-            return isCommentAtParentStart(node, "ObjectExpression") || isCommentAtParentStart(node, "ObjectPattern");
+        function isCommentAtObjectStart(token) {
+            return isCommentAtParentStart(token, "ObjectExpression") || isCommentAtParentStart(token, "ObjectPattern");
         }
 
         /**
          * Returns whether or not comments are at the object end or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @returns {boolean} True if the comment is at object end.
          */
-        function isCommentAtObjectEnd(node) {
-            return isCommentAtParentEnd(node, "ObjectExpression") || isCommentAtParentEnd(node, "ObjectPattern");
+        function isCommentAtObjectEnd(token) {
+            return isCommentAtParentEnd(token, "ObjectExpression") || isCommentAtParentEnd(token, "ObjectPattern");
         }
 
         /**
          * Returns whether or not comments are at the array start or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @returns {boolean} True if the comment is at array start.
          */
-        function isCommentAtArrayStart(node) {
-            return isCommentAtParentStart(node, "ArrayExpression") || isCommentAtParentStart(node, "ArrayPattern");
+        function isCommentAtArrayStart(token) {
+            return isCommentAtParentStart(token, "ArrayExpression") || isCommentAtParentStart(token, "ArrayPattern");
         }
 
         /**
          * Returns whether or not comments are at the array end or not.
-         * @param {ASTNode} node The Comment node.
+         * @param {token} token The Comment token.
          * @returns {boolean} True if the comment is at array end.
          */
-        function isCommentAtArrayEnd(node) {
-            return isCommentAtParentEnd(node, "ArrayExpression") || isCommentAtParentEnd(node, "ArrayPattern");
+        function isCommentAtArrayEnd(token) {
+            return isCommentAtParentEnd(token, "ArrayExpression") || isCommentAtParentEnd(token, "ArrayPattern");
         }
 
         /**
-         * Checks if a comment node has lines around it (ignores inline comments)
-         * @param {ASTNode} node The Comment node.
+         * Checks if a comment token has lines around it (ignores inline comments)
+         * @param {token} token The Comment token.
          * @param {Object} opts Options to determine the newline.
          * @param {boolean} opts.after Should have a newline after this line.
          * @param {boolean} opts.before Should have a newline before this line.
          * @returns {void}
          */
-        function checkForEmptyLine(node, opts) {
-            if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(node.value)) {
+        function checkForEmptyLine(token, opts) {
+            if (applyDefaultIgnorePatterns && defaultIgnoreRegExp.test(token.value)) {
                 return;
             }
 
-            if (ignorePattern && customIgnoreRegExp.test(node.value)) {
+            if (ignorePattern && customIgnoreRegExp.test(token.value)) {
                 return;
             }
 
             let after = opts.after,
                 before = opts.before;
 
-            const prevLineNum = node.loc.start.line - 1,
-                nextLineNum = node.loc.end.line + 1,
-                commentIsNotAlone = codeAroundComment(node);
+            const prevLineNum = token.loc.start.line - 1,
+                nextLineNum = token.loc.end.line + 1,
+                commentIsNotAlone = codeAroundComment(token);
 
-            const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(node),
-                blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(node),
-                objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(node),
-                objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(node),
-                arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(node),
-                arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(node);
+            const blockStartAllowed = options.allowBlockStart && isCommentAtBlockStart(token),
+                blockEndAllowed = options.allowBlockEnd && isCommentAtBlockEnd(token),
+                objectStartAllowed = options.allowObjectStart && isCommentAtObjectStart(token),
+                objectEndAllowed = options.allowObjectEnd && isCommentAtObjectEnd(token),
+                arrayStartAllowed = options.allowArrayStart && isCommentAtArrayStart(token),
+                arrayEndAllowed = options.allowArrayEnd && isCommentAtArrayEnd(token);
 
             const exceptionStartAllowed = blockStartAllowed || objectStartAllowed || arrayStartAllowed;
             const exceptionEndAllowed = blockEndAllowed || objectEndAllowed || arrayEndAllowed;
@@ -319,17 +307,17 @@ module.exports = {
                 return;
             }
 
-            const previousTokenOrComment = sourceCode.getTokenBefore(node, { includeComments: true });
-            const nextTokenOrComment = sourceCode.getTokenAfter(node, { includeComments: true });
+            const previousTokenOrComment = sourceCode.getTokenBefore(token, { includeComments: true });
+            const nextTokenOrComment = sourceCode.getTokenAfter(token, { includeComments: true });
 
             // check for newline before
             if (!exceptionStartAllowed && before && !lodash.includes(commentAndEmptyLines, prevLineNum) &&
-                    !(isCommentNodeType(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, node))) {
-                const lineStart = node.range[0] - node.loc.start.column;
+                    !(astUtils.isCommentToken(previousTokenOrComment) && astUtils.isTokenOnSameLine(previousTokenOrComment, token))) {
+                const lineStart = token.range[0] - token.loc.start.column;
                 const range = [lineStart, lineStart];
 
                 context.report({
-                    node,
+                    node: token,
                     message: "Expected line before comment.",
                     fix(fixer) {
                         return fixer.insertTextBeforeRange(range, "\n");
@@ -339,12 +327,12 @@ module.exports = {
 
             // check for newline after
             if (!exceptionEndAllowed && after && !lodash.includes(commentAndEmptyLines, nextLineNum) &&
-                    !(isCommentNodeType(nextTokenOrComment) && astUtils.isTokenOnSameLine(node, nextTokenOrComment))) {
+                    !(astUtils.isCommentToken(nextTokenOrComment) && astUtils.isTokenOnSameLine(token, nextTokenOrComment))) {
                 context.report({
-                    node,
+                    node: token,
                     message: "Expected line after comment.",
                     fix(fixer) {
-                        return fixer.insertTextAfter(node, "\n");
+                        return fixer.insertTextAfter(token, "\n");
                     }
                 });
             }
@@ -356,25 +344,25 @@ module.exports = {
         //--------------------------------------------------------------------------
 
         return {
-
-            LineComment(node) {
-                if (options.beforeLineComment || options.afterLineComment) {
-                    checkForEmptyLine(node, {
-                        after: options.afterLineComment,
-                        before: options.beforeLineComment
-                    });
-                }
-            },
-
-            BlockComment(node) {
-                if (options.beforeBlockComment || options.afterBlockComment) {
-                    checkForEmptyLine(node, {
-                        after: options.afterBlockComment,
-                        before: options.beforeBlockComment
-                    });
-                }
+            Program() {
+                comments.forEach(token => {
+                    if (token.type === "Line") {
+                        if (options.beforeLineComment || options.afterLineComment) {
+                            checkForEmptyLine(token, {
+                                after: options.afterLineComment,
+                                before: options.beforeLineComment
+                            });
+                        }
+                    } else if (token.type === "Block") {
+                        if (options.beforeBlockComment || options.afterBlockComment) {
+                            checkForEmptyLine(token, {
+                                after: options.afterBlockComment,
+                                before: options.beforeBlockComment
+                            });
+                        }
+                    }
+                });
             }
-
         };
     }
 };
diff --git a/lib/rules/lines-around-directive.js b/lib/rules/lines-around-directive.js
index 89dd9c6ae..af2b3ca66 100644
--- a/lib/rules/lines-around-directive.js
+++ b/lib/rules/lines-around-directive.js
@@ -131,17 +131,12 @@ module.exports = {
             }
 
             const firstDirective = directives[0];
-            const hasTokenOrCommentBefore = !!sourceCode.getTokenBefore(firstDirective, { includeComments: true });
+            const leadingComments = sourceCode.getComments(firstDirective).leading;
 
             // 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
             // the file if there are no comments as well as for compatibility with padded-blocks.
-            if (
-                firstDirective.leadingComments && firstDirective.leadingComments.length ||
-
-                // Shebangs are not added to leading comments but are accounted for by the following.
-                node.type === "Program" && hasTokenOrCommentBefore
-            ) {
+            if (leadingComments.length) {
                 if (expectLineBefore === "always" && !hasNewlineBefore(firstDirective)) {
                     reportError(firstDirective, "before", true);
                 }
@@ -152,7 +147,7 @@ module.exports = {
             } else if (
                 node.type === "Program" &&
                 expectLineBefore === "never" &&
-                !hasTokenOrCommentBefore &&
+                !leadingComments.length &&
                 hasNewlineBefore(firstDirective)
             ) {
                 reportError(firstDirective, "before", false);
diff --git a/lib/rules/no-empty-function.js b/lib/rules/no-empty-function.js
index 115d8698e..38c915c33 100644
--- a/lib/rules/no-empty-function.js
+++ b/lib/rules/no-empty-function.js
@@ -132,11 +132,15 @@ module.exports = {
         function reportIfEmpty(node) {
             const kind = getKind(node);
             const name = astUtils.getFunctionNameWithKind(node);
+            const innerComments = sourceCode.getTokens(node.body, {
+                includeComments: true,
+                filter: astUtils.isCommentToken
+            });
 
             if (allowed.indexOf(kind) === -1 &&
                 node.body.type === "BlockStatement" &&
                 node.body.body.length === 0 &&
-                sourceCode.getComments(node.body).trailing.length === 0
+                innerComments.length === 0
             ) {
                 context.report({
                     node,
diff --git a/lib/rules/no-inline-comments.js b/lib/rules/no-inline-comments.js
index 46815d154..42b4753df 100644
--- a/lib/rules/no-inline-comments.js
+++ b/lib/rules/no-inline-comments.js
@@ -55,10 +55,11 @@ module.exports = {
         //--------------------------------------------------------------------------
 
         return {
+            Program() {
+                const comments = sourceCode.getAllComments();
 
-            LineComment: testCodeAroundComment,
-            BlockComment: testCodeAroundComment
-
+                comments.filter(token => token.type !== "Shebang").forEach(testCodeAroundComment);
+            }
         };
     }
 };
diff --git a/lib/rules/no-irregular-whitespace.js b/lib/rules/no-irregular-whitespace.js
index 3882501a8..6747dd5c8 100644
--- a/lib/rules/no-irregular-whitespace.js
+++ b/lib/rules/no-irregular-whitespace.js
@@ -60,9 +60,6 @@ module.exports = {
         // Module store of errors that we have found
         let errors = [];
 
-        // Comment nodes.  We accumulate these as we go, so we can be sure to trigger them after the whole `Program` entity is parsed, even for top-of-file comments.
-        const commentNodes = [];
-
         // Lookup the `skipComments` option, which defaults to `false`.
         const options = context.options[0] || {};
         const skipComments = !!options.skipComments;
@@ -71,6 +68,7 @@ module.exports = {
         const skipTemplates = !!options.skipTemplates;
 
         const sourceCode = context.getSourceCode();
+        const commentNodes = sourceCode.getAllComments();
 
         /**
          * Removes errors that occur inside a string node
@@ -188,16 +186,6 @@ module.exports = {
             }
         }
 
-        /**
-         * Stores a comment node (`LineComment` or `BlockComment`) for later stripping of errors within; a necessary deferring of processing to deal with top-of-file comments.
-         * @param {ASTNode} node The comment node
-         * @returns {void}
-         * @private
-         */
-        function rememberCommentNode(node) {
-            commentNodes.push(node);
-        }
-
         /**
          * A no-op function to act as placeholder for comment accumulation when the `skipComments` option is `false`.
          * @returns {void}
@@ -220,7 +208,6 @@ module.exports = {
                  * We can later filter the errors when they are found to be not an
                  * issue in nodes we don't care about.
                  */
-
                 checkForIrregularWhitespace(node);
                 checkForIrregularLineTerminators(node);
             };
@@ -228,13 +215,10 @@ module.exports = {
             nodes.Identifier = removeInvalidNodeErrorsInIdentifierOrLiteral;
             nodes.Literal = removeInvalidNodeErrorsInIdentifierOrLiteral;
             nodes.TemplateElement = skipTemplates ? removeInvalidNodeErrorsInTemplateLiteral : noop;
-            nodes.LineComment = skipComments ? rememberCommentNode : noop;
-            nodes.BlockComment = skipComments ? rememberCommentNode : noop;
             nodes["Program:exit"] = function() {
-
                 if (skipComments) {
 
-                    // First strip errors occurring in comment nodes.  We have to do this post-`Program` to deal with top-of-file comments.
+                    // First strip errors occurring in comment nodes.
                     commentNodes.forEach(removeInvalidNodeErrorsInComment);
                 }
 
diff --git a/lib/rules/no-warning-comments.js b/lib/rules/no-warning-comments.js
index bda430868..c0ecaca9e 100644
--- a/lib/rules/no-warning-comments.js
+++ b/lib/rules/no-warning-comments.js
@@ -40,7 +40,8 @@ module.exports = {
 
     create(context) {
 
-        const configuration = context.options[0] || {},
+        const sourceCode = context.getSourceCode(),
+            configuration = context.options[0] || {},
             warningTerms = configuration.terms || ["todo", "fixme", "xxx"],
             location = configuration.location || "start",
             selfConfigRegEx = /\bno-warning-comments\b/;
@@ -128,8 +129,11 @@ module.exports = {
         }
 
         return {
-            BlockComment: checkComment,
-            LineComment: checkComment
+            Program() {
+                const comments = sourceCode.getAllComments();
+
+                comments.filter(token => token.type !== "Shebang").forEach(checkComment);
+            }
         };
     }
 };
diff --git a/lib/rules/spaced-comment.js b/lib/rules/spaced-comment.js
index 4e418fd19..b1e35055c 100644
--- a/lib/rules/spaced-comment.js
+++ b/lib/rules/spaced-comment.js
@@ -229,6 +229,8 @@ module.exports = {
 
     create(context) {
 
+        const sourceCode = context.getSourceCode();
+
         // Unless the first option is never, require a space
         const requireSpace = context.options[0] !== "never";
 
@@ -363,10 +365,11 @@ module.exports = {
         }
 
         return {
+            Program() {
+                const comments = sourceCode.getAllComments();
 
-            LineComment: checkCommentForSpace,
-            BlockComment: checkCommentForSpace
-
+                comments.filter(token => token.type !== "Shebang").forEach(checkCommentForSpace);
+            }
         };
     }
 };
diff --git a/lib/util/comment-event-generator.js b/lib/util/comment-event-generator.js
deleted file mode 100644
index 239a9834d..000000000
--- a/lib/util/comment-event-generator.js
+++ /dev/null
@@ -1,116 +0,0 @@
-/**
- * @fileoverview The event generator for comments.
- * @author Toru Nagashima
- */
-
-"use strict";
-
-//------------------------------------------------------------------------------
-// Helpers
-//------------------------------------------------------------------------------
-
-/**
- * Check collection of comments to prevent double event for comment as
- * leading and trailing, then emit event if passing
- * @param {ASTNode[]} comments - Collection of comment nodes
- * @param {EventEmitter} emitter - The event emitter which is the destination of events.
- * @param {Object[]} locs - List of locations of previous comment nodes
- * @param {string} eventName - Event name postfix
- * @returns {void}
- */
-function emitComments(comments, emitter, locs, eventName) {
-    if (comments.length > 0) {
-        comments.forEach(node => {
-            const index = locs.indexOf(node.loc);
-
-            if (index >= 0) {
-                locs.splice(index, 1);
-            } else {
-                locs.push(node.loc);
-                emitter.emit(node.type + eventName, node);
-            }
-        });
-    }
-}
-
-/**
- * Shortcut to check and emit enter of comment nodes
- * @param {CommentEventGenerator} generator - A generator to emit.
- * @param {ASTNode[]} comments - Collection of comment nodes
- * @returns {void}
- */
-function emitCommentsEnter(generator, comments) {
-    emitComments(
-        comments,
-        generator.emitter,
-        generator.commentLocsEnter,
-        "Comment");
-}
-
-/**
- * Shortcut to check and emit exit of comment nodes
- * @param {CommentEventGenerator} generator - A generator to emit.
- * @param {ASTNode[]} comments Collection of comment nodes
- * @returns {void}
- */
-function emitCommentsExit(generator, comments) {
-    emitComments(
-        comments,
-        generator.emitter,
-        generator.commentLocsExit,
-        "Comment:exit");
-}
-
-//------------------------------------------------------------------------------
-// Public Interface
-//------------------------------------------------------------------------------
-
-/**
- * The event generator for comments.
- * This is the decorator pattern.
- * This generates events of comments before/after events which are generated the original generator.
- *
- * Comment event generator class
- */
-class CommentEventGenerator {
-
-    /**
-     * @param {EventGenerator} originalEventGenerator - An event generator which is the decoration target.
-     * @param {SourceCode} sourceCode - A source code which has comments.
-     */
-    constructor(originalEventGenerator, sourceCode) {
-        this.original = originalEventGenerator;
-        this.emitter = originalEventGenerator.emitter;
-        this.sourceCode = sourceCode;
-        this.commentLocsEnter = [];
-        this.commentLocsExit = [];
-    }
-
-    /**
-     * Emits an event of entering comments.
-     * @param {ASTNode} node - A node which was entered.
-     * @returns {void}
-     */
-    enterNode(node) {
-        const comments = this.sourceCode.getComments(node);
-
-        emitCommentsEnter(this, comments.leading);
-        this.original.enterNode(node);
-        emitCommentsEnter(this, comments.trailing);
-    }
-
-    /**
-     * Emits an event of leaving comments.
-     * @param {ASTNode} node - A node which was left.
-     * @returns {void}
-     */
-    leaveNode(node) {
-        const comments = this.sourceCode.getComments(node);
-
-        emitCommentsExit(this, comments.trailing);
-        this.original.leaveNode(node);
-        emitCommentsExit(this, comments.leading);
-    }
-}
-
-module.exports = CommentEventGenerator;
diff --git a/lib/util/source-code.js b/lib/util/source-code.js
index caf772db6..014713aeb 100644
--- a/lib/util/source-code.js
+++ b/lib/util/source-code.js
@@ -101,7 +101,6 @@ function sortedMerge(tokens, comments) {
     return result;
 }
 
-
 //------------------------------------------------------------------------------
 // Public Interface
 //------------------------------------------------------------------------------
@@ -138,6 +137,16 @@ class SourceCode extends TokenStore {
          */
         this.ast = ast;
 
+        // Check the source text for the presence of a shebang since it is parsed as a standard line comment.
+        const shebangMatched = this.text.match(astUtils.SHEBANG_MATCHER);
+        const hasShebang = shebangMatched && ast.comments.length && ast.comments[0].value === shebangMatched[1];
+
+        if (hasShebang) {
+            ast.comments[0].type = "Shebang";
+        }
+
+        this.tokensAndComments = sortedMerge(ast.tokens, ast.comments);
+
         /**
          * The source code split into lines according to ECMA-262 specification.
          * This is done to avoid each rule needing to do so separately.
@@ -164,7 +173,8 @@ class SourceCode extends TokenStore {
         }
         this.lines.push(this.text.slice(this.lineStartIndices[this.lineStartIndices.length - 1]));
 
-        this.tokensAndComments = sortedMerge(ast.tokens, ast.comments);
+        // Store for comments found using getComments().
+        this._commentStore = new WeakMap();
 
         // don't allow modification of this object
         Object.freeze(this);
@@ -218,61 +228,110 @@ class SourceCode extends TokenStore {
      * @returns {Object} The list of comments indexed by their position.
      * @public
      */
-    getComments(node) { // eslint-disable-line class-methods-use-this
+    getComments(node) {
+        if (this._commentStore.has(node)) {
+            return this._commentStore.get(node);
+        }
 
-        let leadingComments = node.leadingComments || [];
-        const trailingComments = node.trailingComments || [];
+        const comments = {
+            leading: [],
+            trailing: []
+        };
 
         /*
-         * espree adds a "comments" array on Program nodes rather than
-         * leadingComments/trailingComments. Comments are only left in the
-         * Program node comments array if there is no executable code.
+         * Return all comments as leading comments of the Program node when
+         * there is no executable code.
          */
         if (node.type === "Program") {
             if (node.body.length === 0) {
-                leadingComments = node.comments;
+                comments.leading = node.comments;
+            }
+        } else {
+
+            /* Return comments as trailing comments of nodes that only contain
+             * comments (to mimic the comment attachment behavior present in Espree).
+             */
+            if ((node.type === "BlockStatement" || node.type === "ClassBody") && node.body.length === 0 ||
+                node.type === "ObjectExpression" && node.properties.length === 0 ||
+                node.type === "ArrayExpression" && node.elements.length === 0 ||
+                node.type === "SwitchStatement" && node.cases.length === 0
+            ) {
+                comments.trailing = this.getTokens(node, {
+                    includeComments: true,
+                    filter: astUtils.isCommentToken
+                });
+            }
+
+            /*
+             * Iterate over tokens before and after node and collect comment tokens.
+             * Do not include comments that exist outside of the parent node
+             * to avoid duplication.
+             */
+            let currentToken = this.getTokenBefore(node, { includeComments: true });
+
+            while (currentToken && astUtils.isCommentToken(currentToken)) {
+                if (node.parent && (currentToken.start < node.parent.start)) {
+                    break;
+                }
+                comments.leading.unshift(currentToken);
+                currentToken = this.getTokenBefore(currentToken, { includeComments: true });
+            }
+
+            currentToken = this.getTokenAfter(node, { includeComments: true });
+
+            while (currentToken && astUtils.isCommentToken(currentToken)) {
+                if (node.parent && (currentToken.end > node.parent.end)) {
+                    break;
+                }
+                comments.trailing.push(currentToken);
+                currentToken = this.getTokenAfter(currentToken, { includeComments: true });
             }
         }
 
-        return {
-            leading: leadingComments,
-            trailing: trailingComments
-        };
+        this._commentStore.set(node, comments);
+        return comments;
     }
 
     /**
      * Retrieves the JSDoc comment for a given node.
      * @param {ASTNode} node The AST node to get the comment for.
-     * @returns {ASTNode} The BlockComment node containing the JSDoc for the
+     * @returns {ASTNode} The Block comment node containing the JSDoc for the
      *      given node or null if not found.
      * @public
      */
-    getJSDocComment(node) { // eslint-disable-line class-methods-use-this
-
+    getJSDocComment(node) {
         let parent = node.parent;
+        const leadingComments = this.getComments(node).leading;
 
         switch (node.type) {
             case "ClassDeclaration":
             case "FunctionDeclaration":
                 if (looksLikeExport(parent)) {
-                    return findJSDocComment(parent.leadingComments, parent.loc.start.line);
+                    return findJSDocComment(this.getComments(parent).leading, parent.loc.start.line);
                 }
-                return findJSDocComment(node.leadingComments, node.loc.start.line);
+                return findJSDocComment(leadingComments, node.loc.start.line);
 
             case "ClassExpression":
-                return findJSDocComment(parent.parent.leadingComments, parent.parent.loc.start.line);
+                return findJSDocComment(this.getComments(parent.parent).leading, parent.parent.loc.start.line);
 
             case "ArrowFunctionExpression":
             case "FunctionExpression":
-
                 if (parent.type !== "CallExpression" && parent.type !== "NewExpression") {
-                    while (parent && !parent.leadingComments && !/Function/.test(parent.type) && parent.type !== "MethodDefinition" && parent.type !== "Property") {
+                    let parentLeadingComments = this.getComments(parent).leading;
+
+                    while (!parentLeadingComments.length && !/Function/.test(parent.type) && parent.type !== "MethodDefinition" && parent.type !== "Property") {
                         parent = parent.parent;
+
+                        if (!parent) {
+                            break;
+                        }
+
+                        parentLeadingComments = this.getComments(parent).leading;
                     }
 
-                    return parent && (parent.type !== "FunctionDeclaration") ? findJSDocComment(parent.leadingComments, parent.loc.start.line) : null;
-                } else if (node.leadingComments) {
-                    return findJSDocComment(node.leadingComments, node.loc.start.line);
+                    return parent && (parent.type !== "FunctionDeclaration") ? findJSDocComment(parentLeadingComments, parent.loc.start.line) : null;
+                } else if (leadingComments.length) {
+                    return findJSDocComment(leadingComments, node.loc.start.line);
                 }
 
             // falls through


Test patch:
diff --git a/tests/lib/ast-utils.js b/tests/lib/ast-utils.js
index 626497e77..00986a475 100644
--- a/tests/lib/ast-utils.js
+++ b/tests/lib/ast-utils.js
@@ -295,40 +295,60 @@ describe("ast-utils", () => {
         }
 
         it("should return false if it is not a directive line comment", () => {
-            eslint.reset();
-            eslint.on("LineComment", assertFalse);
-            eslint.verify("// lalala I'm a normal comment", {}, filename, true);
-            eslint.verify("// trying to confuse eslint ", {}, filename, true);
-            eslint.verify("//trying to confuse eslint-directive-detection", {}, filename, true);
-            eslint.verify("//eslint is awesome", {}, filename, true);
+            const code = [
+                "// lalala I'm a normal comment",
+                "// trying to confuse eslint ",
+                "//trying to confuse eslint-directive-detection",
+                "//eslint is awesome"
+            ].join("\n");
+            const ast = espree.parse(code, ESPREE_CONFIG);
+            const sourceCode = new SourceCode(code, ast);
+            const comments = sourceCode.getAllComments();
+
+            comments.forEach(assertFalse);
         });
 
         it("should return false if it is not a directive block comment", () => {
-            eslint.reset();
-            eslint.on("BlockComment", assertFalse);
-            eslint.verify("/* lalala I'm a normal comment */", {}, filename, true);
-            eslint.verify("/* trying to confuse eslint */", {}, filename, true);
-            eslint.verify("/* trying to confuse eslint-directive-detection */", {}, filename, true);
-            eslint.verify("/*eSlInT is awesome*/", {}, filename, true);
+            const code = [
+                "/* lalala I'm a normal comment */",
+                "/* trying to confuse eslint */",
+                "/* trying to confuse eslint-directive-detection */",
+                "/*eSlInT is awesome*/"
+            ].join("\n");
+            const ast = espree.parse(code, ESPREE_CONFIG);
+            const sourceCode = new SourceCode(code, ast);
+            const comments = sourceCode.getAllComments();
+
+            comments.forEach(assertFalse);
         });
 
         it("should return true if it is a directive line comment", () => {
-            eslint.reset();
-            eslint.on("LineComment", assertTrue);
-            eslint.verify("// eslint-disable-line no-undef", {}, filename, true);
-            eslint.verify("// eslint-secret-directive 4 8 15 16 23 42   ", {}, filename, true);
-            eslint.verify("// eslint-directive-without-argument", {}, filename, true);
-            eslint.verify("//eslint-directive-without-padding", {}, filename, true);
+            const code = [
+                "// eslint-disable-line no-undef",
+                "// eslint-secret-directive 4 8 15 16 23 42   ",
+                "// eslint-directive-without-argument",
+                "//eslint-directive-without-padding"
+            ].join("\n");
+            const ast = espree.parse(code, ESPREE_CONFIG);
+            const sourceCode = new SourceCode(code, ast);
+            const comments = sourceCode.getAllComments();
+
+            comments.forEach(assertTrue);
         });
 
         it("should return true if it is a directive block comment", () => {
-            eslint.reset();
-            eslint.on("BlockComment", assertTrue);
-            eslint.verify("/* eslint-disable no-undef", {}, filename, true);
-            eslint.verify("/*eslint-enable no-undef", {}, filename, true);
-            eslint.verify("/* eslint-env {\"es6\": true}", {}, filename, true);
-            eslint.verify("/* eslint foo", {}, filename, true);
-            eslint.verify("/*eslint bar", {}, filename, true);
+            const code = [
+                "/* eslint-disable no-undef */",
+                "/*eslint-enable no-undef*/",
+                "/* eslint-env {\"es6\": true} */",
+                "/* eslint foo */",
+                "/*eslint bar*/"
+            ].join("\n");
+            const ast = espree.parse(code, ESPREE_CONFIG);
+            const sourceCode = new SourceCode(code, ast);
+            const comments = sourceCode.getAllComments();
+
+            comments.forEach(assertTrue);
         });
     });
 
diff --git a/tests/lib/eslint.js b/tests/lib/eslint.js
index 3fdf40347..ab98999da 100644
--- a/tests/lib/eslint.js
+++ b/tests/lib/eslint.js
@@ -2615,13 +2615,14 @@ describe("eslint", () => {
             assert.equal(messages[0].line, 3);
         });
 
-        it("should not have a comment with the shebang in it", () => {
+        it("should have a comment with the shebang in it", () => {
             const config = { rules: { "no-extra-semi": 1 } };
 
             eslint.reset();
 
             eslint.on("Program", node => {
-                assert.equal(node.comments.length, 0);
+                assert.equal(node.comments.length, 1);
+                assert.equal(node.comments[0].type, "Shebang");
 
                 let comments = eslint.getComments(node);
 
@@ -2629,20 +2630,12 @@ describe("eslint", () => {
                 assert.equal(comments.trailing.length, 0);
 
                 comments = eslint.getComments(node.body[0]);
-                assert.equal(comments.leading.length, 0);
+                assert.equal(comments.leading.length, 1);
                 assert.equal(comments.trailing.length, 0);
+                assert.equal(comments.leading[0].type, "Shebang");
             });
             eslint.verify(code, config, "foo.js", true);
         });
-
-        it("should not fire a LineComment event for a comment with the shebang in it", () => {
-            const config = { rules: { "no-extra-semi": 1 } };
-
-            eslint.reset();
-
-            eslint.on("LineComment", sandbox.mock().never());
-            eslint.verify(code, config, "foo.js", true);
-        });
     });
 
     describe("when evaluating broken code", () => {
@@ -2881,7 +2874,6 @@ describe("eslint", () => {
     });
 
     describe("when evaluating code with comments to change config when allowInlineConfig is enabled", () => {
-
         it("should report a violation for disabling rules", () => {
             const code = [
                 "alert('test'); // eslint-disable-line no-alert"
@@ -3028,7 +3020,6 @@ describe("eslint", () => {
     });
 
     describe("when evaluating code with comments to change config when allowInlineConfig is disabled", () => {
-
         it("should not report a violation", () => {
             const code = [
                 "alert('test'); // eslint-disable-line no-alert"
@@ -3048,38 +3039,6 @@ describe("eslint", () => {
         });
     });
 
-    describe("when evaluating code with code comments", () => {
-
-        it("should emit enter only once for each comment", () => {
-
-            const code = "a; /*zz*/ b;";
-
-            const config = { rules: {} },
-                spy = sandbox.spy();
-
-            eslint.reset();
-            eslint.on("BlockComment", spy);
-
-            eslint.verify(code, config, filename, true);
-            assert.equal(spy.calledOnce, true);
-        });
-
-        it("should emit exit only once for each comment", () => {
-
-            const code = "a; //zz\n b;";
-
-            const config = { rules: {} },
-                spy = sandbox.spy();
-
-            eslint.reset();
-            eslint.on("LineComment:exit", spy);
-
-            eslint.verify(code, config, filename, true);
-            assert.equal(spy.calledOnce, true);
-        });
-
-    });
-
     describe("when evaluating code with hashbang", () => {
         it("should comment hashbang without breaking offset", () => {
 
@@ -3094,7 +3053,6 @@ describe("eslint", () => {
 
             eslint.verify(code, config, filename, true);
         });
-
     });
 
     describe("verify()", () => {
diff --git a/tests/lib/rules/capitalized-comments.js b/tests/lib/rules/capitalized-comments.js
index 5cb6e7383..ae798a8d5 100644
--- a/tests/lib/rules/capitalized-comments.js
+++ b/tests/lib/rules/capitalized-comments.js
@@ -80,6 +80,14 @@ ruleTester.run("capitalized-comments", rule, {
         "/* globals var1:true, var2 */",
         "/* exported myVar */",
 
+        // Ignores shebangs
+        "#!foo",
+        { code: "#!foo", options: ["always"] },
+        { code: "#!Foo", options: ["never"] },
+        "#!/usr/bin/env node",
+        { code: "#!/usr/bin/env node", options: ["always"] },
+        { code: "#!/usr/bin/env node", options: ["never"] },
+
         // Using "always" string option
         { code: "//Uppercase", options: ["always"] },
         { code: "// Uppercase", options: ["always"] },
diff --git a/tests/lib/util/comment-event-generator.js b/tests/lib/util/comment-event-generator.js
deleted file mode 100644
index ee4fd031c..000000000
--- a/tests/lib/util/comment-event-generator.js
+++ /dev/null
@@ -1,84 +0,0 @@
-/**
- * @fileoverview Tests for CommentEventGenerator.
- * @author Toru Nagashima
- */
-"use strict";
-
-//------------------------------------------------------------------------------
-// Requirements
-//------------------------------------------------------------------------------
-
-const assert = require("assert"),
-    EventEmitter = require("events").EventEmitter,
-    sinon = require("sinon"),
-    espree = require("espree"),
-    EventGeneratorTester = require("../../../lib/testers/event-generator-tester"),
-    estraverse = require("estraverse"),
-    SourceCode = require("../../../lib/util/source-code"),
-    NodeEventGenerator = require("../../../lib/util/node-event-generator"),
-    CommentEventGenerator = require("../../../lib/util/comment-event-generator");
-
-//------------------------------------------------------------------------------
-// Tests
-//------------------------------------------------------------------------------
-
-describe("NodeEventGenerator", () => {
-    EventGeneratorTester.testEventGeneratorInterface(
-        new CommentEventGenerator(new NodeEventGenerator(new EventEmitter()))
-    );
-
-    it("should generate comment events without duplicate.", () => {
-        const emitter = new EventEmitter();
-        const code = "//foo\nvar zzz /*aaa*/ = 777;\n//bar";
-        const ast = espree.parse(code, {
-            range: true,
-            loc: true,
-            comments: true,
-            attachComment: true,
-            tokens: true
-        });
-        const sourceCode = new SourceCode(code, ast);
-        const expected = [
-
-            ["Program", ast],
-            ["LineComment", ast.comments[0]], // foo
-            ["VariableDeclaration", ast.body[0]],
-            ["LineComment", ast.comments[2]], // bar
-            ["VariableDeclarator", ast.body[0].declarations[0]],
-            ["Identifier", ast.body[0].declarations[0].id],
-            ["BlockComment", ast.comments[1]], /* aaa */
-            ["BlockComment:exit", ast.comments[1]], /* aaa */
-            ["Identifier:exit", ast.body[0].declarations[0].id],
-            ["Literal", ast.body[0].declarations[0].init],
-            ["Literal:exit", ast.body[0].declarations[0].init],
-            ["VariableDeclarator:exit", ast.body[0].declarations[0]],
-            ["LineComment:exit", ast.comments[2]], // bar
-            ["VariableDeclaration:exit", ast.body[0]],
-            ["LineComment:exit", ast.comments[0]], // foo
-            ["Program:exit", ast]
-        ];
-
-        expected.forEach(expectedValues => emitter.on(expectedValues[0], () => {}));
-
-        let generator = new NodeEventGenerator(emitter);
-
-        emitter.emit = sinon.spy(emitter.emit);
-        generator = new CommentEventGenerator(generator, sourceCode);
-
-        estraverse.traverse(ast, {
-            enter(node) {
-                generator.enterNode(node);
-            },
-            leave(node) {
-                generator.leaveNode(node);
-            }
-        });
-
-        assert.equal(emitter.emit.callCount, expected.length);
-
-        for (let i = 0; i < expected.length; ++i) {
-            assert.equal(emitter.emit.args[i][0], expected[i][0]);
-            assert.equal(emitter.emit.args[i][1], expected[i][1]);
-        }
-    });
-});
diff --git a/tests/lib/util/source-code.js b/tests/lib/util/source-code.js
index 03dd189d6..1f7177eb2 100644
--- a/tests/lib/util/source-code.js
+++ b/tests/lib/util/source-code.js
@@ -15,7 +15,8 @@ const fs = require("fs"),
     sinon = require("sinon"),
     leche = require("leche"),
     eslint = require("../../../lib/eslint"),
-    SourceCode = require("../../../lib/util/source-code");
+    SourceCode = require("../../../lib/util/source-code"),
+    astUtils = require("../../../lib/ast-utils");
 
 //------------------------------------------------------------------------------
 // Helpers
@@ -30,7 +31,8 @@ const DEFAULT_CONFIG = {
 };
 
 const AST = espree.parse("let foo = bar;", DEFAULT_CONFIG),
-    TEST_CODE = "var answer = 6 * 7;";
+    TEST_CODE = "var answer = 6 * 7;",
+    SHEBANG_TEST_CODE = `#!/usr/bin/env node\n${TEST_CODE}`;
 
 //------------------------------------------------------------------------------
 // Tests
@@ -146,6 +148,32 @@ describe("SourceCode", () => {
             });
         });
 
+        describe("when a text has a shebang", () => {
+            let sourceCode;
+
+            beforeEach(() => {
+                const ast = { comments: [{ type: "Line", value: "/usr/bin/env node", range: [0, 19] }], tokens: [], loc: {}, range: [] };
+
+                sourceCode = new SourceCode(SHEBANG_TEST_CODE, ast);
+            });
+
+            it("should change the type of the first comment to \"Shebang\"", () => {
+                const firstToken = sourceCode.getAllComments()[0];
+
+                assert.equal(firstToken.type, "Shebang");
+            });
+        });
+
+        describe("when a text does not have a shebang", () => {
+            it("should not change the type of the first comment", () => {
+                const ast = { comments: [{ type: "Line", value: "comment", range: [0, 9] }], tokens: [], loc: {}, range: [] };
+                const sourceCode = new SourceCode("//comment\nconsole.log('hello');", ast);
+                const firstToken = sourceCode.getAllComments()[0];
+
+                assert.equal(firstToken.type, "Line");
+            });
+        });
+
         describe("when it read a UTF-8 file (has BOM), SourceCode", () => {
             const UTF8_FILE = path.resolve(__dirname, "../../fixtures/utf8-bom.js");
             const text = fs.readFileSync(
@@ -889,14 +917,17 @@ describe("SourceCode", () => {
             };
         }
 
-        it("should attach them to all nodes", () => {
+        beforeEach(() => {
+            eslint.reset();
+        });
+
+        it("should return comments around nodes", () => {
             const code = [
-                "// my line comment",
+                "// Leading comment for VariableDeclaration",
                 "var a = 42;",
-                "/* my block comment */"
+                "/* Trailing comment for VariableDeclaration */"
             ].join("\n");
 
-            eslint.reset();
             eslint.on("Program", assertCommentCount(0, 0));
             eslint.on("VariableDeclaration", assertCommentCount(1, 1));
             eslint.on("VariableDeclarator", assertCommentCount(0, 0));
@@ -906,17 +937,47 @@ describe("SourceCode", () => {
             eslint.verify(code, config, "", true);
         });
 
-        it("should not attach leading comments from previous node", () => {
+        it("should return trailing comments inside a block", () => {
+            const code = [
+                "{",
+                "    a();",
+                "    // Trailing comment for ExpressionStatement",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("ExpressionStatement", assertCommentCount(0, 1));
+            eslint.on("CallExpression", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments within a conditional", () => {
+            const code = [
+                "/* Leading comment for IfStatement */",
+                "if (/* Leading comment for Identifier */ a) {}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("IfStatement", assertCommentCount(1, 0));
+            eslint.on("Identifier", assertCommentCount(1, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should not return comments within a previous node", () => {
             const code = [
                 "function a() {",
                 "    var b = {",
-                "        // comment",
+                "        // Trailing comment for ObjectExpression",
                 "    };",
                 "    return b;",
                 "}"
             ].join("\n");
 
-            eslint.reset();
             eslint.on("Program", assertCommentCount(0, 0));
             eslint.on("Identifier", assertCommentCount(0, 0));
             eslint.on("BlockStatement", assertCommentCount(0, 0));
@@ -928,19 +989,528 @@ describe("SourceCode", () => {
             eslint.verify(code, config, "", true);
         });
 
-        it("should not attach duplicate leading comments from previous node", () => {
+        it("should return comments only for children of parent node", () => {
             const code = [
-                "//foo",
-                "var zzz /*aaa*/ = 777;",
-                "//bar"
+                "var foo = {",
+                "    bar: 'bar'",
+                "    // Trailing comment for Property",
+                "};",
+                "var baz;"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("VariableDeclaration", assertCommentCount(0, 0));
+            eslint.on("VariableDeclerator", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("ObjectExpression", assertCommentCount(0, 0));
+            eslint.on("Property", assertCommentCount(0, 1));
+            eslint.on("Literal", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments for an export default anonymous class", () => {
+            const code = [
+                "/**",
+                " * Leading comment for ExportDefaultDeclaration",
+                " */",
+                "export default class {",
+                "    /**",
+                "     * Leading comment for MethodDefinition",
+                "     */",
+                "    method1(){",
+                "    }",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("ExportDefaultDeclaration", assertCommentCount(1, 0));
+            eslint.on("ClassDeclaration", assertCommentCount(0, 0));
+            eslint.on("ClassBody", assertCommentCount(0, 0));
+            eslint.on("MethodDefinition", assertCommentCount(1, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("FunctionExpression", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return leading comments", () => {
+            const code = [
+                "// Leading comment for first VariableDeclaration",
+                "var a;",
+                "// Leading comment for previous VariableDeclaration and trailing comment for next VariableDeclaration",
+                "var b;"
+            ].join("\n");
+            let varDeclCount = 0;
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("VariableDeclaration", node => {
+                if (varDeclCount === 0) {
+                    assertCommentCount(1, 1)(node);
+                } else {
+                    assertCommentCount(1, 0)(node);
+                }
+                varDeclCount++;
+            });
+            eslint.on("VariableDeclarator", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return shebang comments", () => {
+            const code = [
+                "#!/usr/bin/env node", // Leading comment for following VariableDeclaration
+                "var a;",
+                "// Leading comment for previous VariableDeclaration and trailing comment for next VariableDeclaration",
+                "var b;"
+            ].join("\n");
+            let varDeclCount = 0;
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("VariableDeclaration", node => {
+                if (varDeclCount === 0) {
+                    assertCommentCount(1, 1)(node);
+                } else {
+                    assertCommentCount(1, 0)(node);
+                }
+                varDeclCount++;
+            });
+            eslint.on("VariableDeclarator", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should include shebang comment when program only contains shebang", () => {
+            const code = "#!/usr/bin/env node";
+
+            eslint.on("Program", assertCommentCount(1, 0));
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return mixture of line and block comments", () => {
+            const code = [
+                "// Leading comment for VariableDeclaration",
+                "var zzz /* Trailing comment for Identifier */ = 777;",
+                "// Trailing comment for VariableDeclaration"
             ].join("\n");
 
-            eslint.reset();
             eslint.on("Program", assertCommentCount(0, 0));
             eslint.on("VariableDeclaration", assertCommentCount(1, 1));
             eslint.on("VariableDeclarator", assertCommentCount(0, 0));
             eslint.on("Identifier", assertCommentCount(0, 1));
-            eslint.on("Literal", assertCommentCount(1, 0));
+            eslint.on("Literal", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments surrounding a call expression", () => {
+            const code = [
+                "function a() {",
+                "    /* Leading comment for ExpressionStatement */",
+                "    foo();",
+                "    /* Trailing comment for ExpressionStatement */",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("ExpressionStatement", assertCommentCount(1, 1));
+            eslint.on("CallExpression", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments surrounding a debugger statement", () => {
+            const code = [
+                "function a() {",
+                "    /* Leading comment for DebuggerStatement */",
+                "    debugger;",
+                "    /* Trailing comment for DebuggerStatement */",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("DebuggerStatement", assertCommentCount(1, 1));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments surrounding a return statement", () => {
+            const code = [
+                "function a() {",
+                "    /* Leading comment for ReturnStatement */",
+                "    return;",
+                "    /* Trailing comment for ReturnStatement */",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("ReturnStatement", assertCommentCount(1, 1));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments surrounding a throw statement", () => {
+            const code = [
+                "function a() {",
+                "    /* Leading comment for ThrowStatement */",
+                "    throw 55;",
+                "    /* Trailing comment for ThrowStatement */",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("ThrowStatement", assertCommentCount(1, 1));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments surrounding a while loop", () => {
+            const code = [
+                "function f() {",
+                "    /* Leading comment for WhileStatement */",
+                "    while (true) {}",
+                "    /* Trailing comment for WhileStatement and leading comment for VariableDeclaration */",
+                "    var each;",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("WhileStatement", assertCommentCount(1, 1));
+            eslint.on("Literal", assertCommentCount(0, 0));
+            eslint.on("VariableDeclaration", assertCommentCount(1, 0));
+            eslint.on("VariableDeclarator", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return switch case fallthrough comments in functions", () => {
+            const code = [
+                "function bar(foo) {",
+                "    switch(foo) {",
+                "    /* Leading comment for SwitchCase */",
+                "    case 1:",
+                "        // falls through", // Trailing comment for previous SwitchCase and leading comment for next SwitchCase
+                "    case 2:",
+                "        doIt();",
+                "    }",
+                "}"
+            ].join("\n");
+            let switchCaseCount = 0;
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("SwitchStatement", assertCommentCount(0, 0));
+            eslint.on("SwitchCase", node => {
+                if (switchCaseCount === 0) {
+                    assertCommentCount(1, 1)(node);
+                } else {
+                    assertCommentCount(1, 0)(node);
+                }
+                switchCaseCount++;
+            });
+            eslint.on("Literal", assertCommentCount(0, 0));
+            eslint.on("ExpressionStatement", assertCommentCount(0, 0));
+            eslint.on("CallExpression", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return switch case fallthrough comments", () => {
+            const code = [
+                "switch(foo) {",
+                "    /* Leading comment for SwitchCase */",
+                "case 1:",
+                "    // falls through", // Trailing comment for previous SwitchCase and leading comment for next SwitchCase
+                "case 2:",
+                "    doIt();",
+                "}"
+            ].join("\n");
+            let switchCaseCount = 0;
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("SwitchStatement", assertCommentCount(0, 0));
+            eslint.on("SwitchCase", node => {
+                if (switchCaseCount === 0) {
+                    assertCommentCount(1, 1)(node);
+                } else {
+                    assertCommentCount(1, 0)(node);
+                }
+                switchCaseCount++;
+            });
+            eslint.on("Literal", assertCommentCount(0, 0));
+            eslint.on("ExpressionStatement", assertCommentCount(0, 0));
+            eslint.on("CallExpression", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return switch case no-default comments in functions", () => {
+            const code = [
+                "function bar(a) {",
+                "    switch (a) {",
+                "        case 2:",
+                "            break;",
+                "        case 1:",
+                "            break;",
+                "        // no default", // Trailing comment for SwitchCase
+                "    }",
+                "}"
+            ].join("\n");
+            let breakStatementCount = 0;
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("SwitchStatement", assertCommentCount(0, 0));
+            eslint.on("SwitchCase", node => {
+                if (breakStatementCount === 0) {
+                    assertCommentCount(0, 0)(node);
+                } else {
+                    assertCommentCount(0, 1)(node);
+                }
+                breakStatementCount++;
+            });
+            eslint.on("BreakStatement", assertCommentCount(0, 0));
+            eslint.on("Literal", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return switch case no-default comments", () => {
+            const code = [
+                "switch (a) {",
+                "    case 1:",
+                "        break;",
+                "    // no default", // Trailing comment for SwitchCase
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("SwitchStatement", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("SwitchCase", assertCommentCount(0, 1));
+            eslint.on("BreakStatement", assertCommentCount(0, 0));
+            eslint.on("Literal", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return switch case no-default comments in nested functions", () => {
+            const code = [
+                "module.exports = function(context) {",
+                "    function isConstant(node) {",
+                "        switch (node.type) {",
+                "            case 'SequenceExpression':",
+                "                return isConstant(node.expressions[node.expressions.length - 1]);",
+                "            // no default", // Trailing comment for SwitchCase
+                "        }",
+                "        return false;",
+                "    }",
+                "};"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("ExpressionStatement", assertCommentCount(0, 0));
+            eslint.on("AssignmentExpression", assertCommentCount(0, 0));
+            eslint.on("MemberExpression", assertCommentCount(0, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+            eslint.on("FunctionExpression", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(0, 0));
+            eslint.on("SwitchStatement", assertCommentCount(0, 0));
+            eslint.on("SwitchCase", assertCommentCount(0, 1));
+            eslint.on("ReturnStatement", assertCommentCount(0, 0));
+            eslint.on("CallExpression", assertCommentCount(0, 0));
+            eslint.on("BinaryExpression", assertCommentCount(0, 0));
+            eslint.on("Literal", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return leading comments if the code only contains comments", () => {
+            const code = [
+                "//comment",
+                "/*another comment*/"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(2, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return trailing comments if a block statement only contains comments", () => {
+            const code = [
+                "{",
+                "    //comment",
+                "    /*another comment*/",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("BlockStatement", assertCommentCount(0, 2));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return trailing comments if a class body only contains comments", () => {
+            const code = [
+                "class Foo {",
+                "    //comment",
+                "    /*another comment*/",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("ClassDeclaration", assertCommentCount(0, 0));
+            eslint.on("ClassBody", assertCommentCount(0, 2));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return trailing comments if an object only contains comments", () => {
+            const code = [
+                "({",
+                "    //comment",
+                "    /*another comment*/",
+                "})"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("ExpressionStatement", assertCommentCount(0, 0));
+            eslint.on("ObjectExpression", assertCommentCount(0, 2));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return trailing comments if an array only contains comments", () => {
+            const code = [
+                "[",
+                "    //comment",
+                "    /*another comment*/",
+                "]"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("ExpressionStatement", assertCommentCount(0, 0));
+            eslint.on("ArrayExpression", assertCommentCount(0, 2));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return trailing comments if a switch statement only contains comments", () => {
+            const code = [
+                "switch (foo) {",
+                "    //comment",
+                "    /*another comment*/",
+                "}"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("SwitchStatement", assertCommentCount(0, 2));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments for multiple declarations with a single variable", () => {
+            const code = [
+                "// Leading comment for VariableDeclaration",
+                "var a, // Leading comment for next VariableDeclarator",
+                "    b, // Leading comment for next VariableDeclarator",
+                "    c; // Trailing comment for VariableDeclaration",
+                "// Trailing comment for VariableDeclaration"
+            ].join("\n");
+            let varDeclCount = 0;
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("VariableDeclaration", assertCommentCount(1, 2));
+            eslint.on("VariableDeclarator", node => {
+                if (varDeclCount === 0) {
+                    assertCommentCount(0, 0)(node);
+                } else if (varDeclCount === 1) {
+                    assertCommentCount(1, 0)(node);
+                } else {
+                    assertCommentCount(1, 0)(node);
+                }
+                varDeclCount++;
+            });
+            eslint.on("Identifier", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return comments when comments exist between var keyword and VariableDeclarator", () => {
+            const code = [
+                "var // Leading comment for VariableDeclarator",
+                "    // Leading comment for VariableDeclarator",
+                "    a;"
+            ].join("\n");
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("VariableDeclaration", assertCommentCount(0, 0));
+            eslint.on("VariableDeclarator", assertCommentCount(2, 0));
+            eslint.on("Identifier", assertCommentCount(0, 0));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return attached comments between tokens to the correct nodes for empty function declarations", () => {
+            const code = "/* 1 */ function /* 2 */ foo(/* 3 */) /* 4 */ { /* 5 */ } /* 6 */";
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("FunctionDeclaration", assertCommentCount(1, 1));
+            eslint.on("Identifier", assertCommentCount(1, 0));
+            eslint.on("BlockStatement", assertCommentCount(1, 1));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return attached comments between tokens to the correct nodes for empty class declarations", () => {
+            const code = "/* 1 */ class /* 2 */ Foo /* 3 */ extends /* 4 */ Bar /* 5 */ { /* 6 */ } /* 7 */";
+            let idCount = 0;
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("ClassDeclaration", assertCommentCount(1, 1));
+            eslint.on("Identifier", node => {
+                if (idCount === 0) {
+                    assertCommentCount(1, 1)(node);
+                } else {
+                    assertCommentCount(1, 1)(node);
+                }
+                idCount++;
+            });
+            eslint.on("ClassBody", assertCommentCount(1, 1));
+
+            eslint.verify(code, config, "", true);
+        });
+
+        it("should return attached comments between tokens to the correct nodes for empty switch statements", () => {
+            const code = "/* 1 */ switch /* 2 */ (/* 3 */ foo /* 4 */) /* 5 */ { /* 6 */ } /* 7 */";
+
+            eslint.on("Program", assertCommentCount(0, 0));
+            eslint.on("SwitchStatement", assertCommentCount(1, 6));
+            eslint.on("Identifier", assertCommentCount(1, 1));
 
             eslint.verify(code, config, "", true);
         });
@@ -1002,7 +1572,6 @@ describe("SourceCode", () => {
             assert.equal(lines[0], "a;");
             assert.equal(lines[1], "b;");
         });
-
     });
 
     describe("getText()", () => {
@@ -1010,6 +1579,21 @@ describe("SourceCode", () => {
         let sourceCode,
             ast;
 
+        describe("when text begins with a shebang", () => {
+            it("should retrieve unaltered shebang text", () => {
+
+                // Shebangs are normalized to line comments before parsing.
+                ast = espree.parse(SHEBANG_TEST_CODE.replace(astUtils.SHEBANG_MATCHER, (match, captured) => `//${captured}`), DEFAULT_CONFIG);
+                sourceCode = new SourceCode(SHEBANG_TEST_CODE, ast);
+
+                const shebangToken = sourceCode.getAllComments()[0];
+                const shebangText = sourceCode.getText(shebangToken);
+
+                assert.equal(shebangToken.type, "Shebang");
+                assert.equal(shebangText, "#!/usr/bin/env node");
+            });
+        });
+
         beforeEach(() => {
             ast = espree.parse(TEST_CODE, DEFAULT_CONFIG);
             sourceCode = new SourceCode(TEST_CODE, ast);