Repository: eslint/eslint
Pull request #9831 (2018-01-08 23:37:58)

Update: use doctrine range information in valid-jsdoc
<!--
    ESLint adheres to the [JS Foundation Code of Conduct](https://js.foundation/community/code-of-conduct).
-->

**What is the purpose of this pull request? (put an "X" next to item)**

[x] Changes an existing rule
[x] Add autofixing to a rule

**What rule do you want to change?**

`valid-jsdoc`

**Does this change cause the rule to produce more or fewer warnings?**

The same amount

**How will the change be implemented? (New option, new default behavior, etc.)?**

New default behavior

**Please provide some example code that this change will affect:**

```js
/**
 * @param {number} a
 * @param {number} notB
 */
function foo(a, b) {}
```

**What does the rule currently do for this code?**

It reports the entire JSDoc comment to indicate that the second parameter name is wrong.

**What will the rule do after it's changed?**

It will report only the text `@param {number} notB` in the comment to indicate that the second parameter name is wrong.

**What changes did you make? (Give an overview)**

Now that [`doctrine`](https://github.com/eslint/doctrine) can provide range information in JSDoc tags, this makes a few improvements to the `valid-jsdoc` rule:

* When reporting a specific tag or type annotation, the rule now uses that tag as the report location, rather than reporting the entire comment.
* The rule can now autofix type annotations based on the existing `prefer` and `preferType` options (for example, it can replace `object` with `Object` if the rule is configured to prefer the latter).

**Is there anything you'd like reviewers to focus on?**

Nothing in particular

  
  

j-f1: Would it be possible to only report the `notB` segment of the comment?
not-an-aardvark: @j-f1 Unfortunately, that information is not provided by doctrine because it's a string property of the tag object, so it doesn't have its own `range`.

Patch:
diff --git a/docs/developer-guide/nodejs-api.md b/docs/developer-guide/nodejs-api.md
index b72425262..b774d3694 100644
--- a/docs/developer-guide/nodejs-api.md
+++ b/docs/developer-guide/nodejs-api.md
@@ -820,9 +820,9 @@ In addition to the properties above, invalid test cases can also have the follow
     * `message` (string/regexp): The message for the error
     * `type` (string): The type of the reported AST node
     * `line` (number): The 1-based line number of the reported location
-    * `column` (number): The 0-based column number of the reported location
+    * `column` (number): The 1-based column number of the reported location
     * `endLine` (number): The 1-based line number of the end of the reported location
-    * `endColumn` (number): The 0-based column number of the end of the reported location
+    * `endColumn` (number): The 1-based column number of the end of the reported location
 
     If a string is provided as an error instead of an object, the string is used to assert the `message` of the error.
 * `output` (string, optional): Asserts the output that will be produced when using this rule for a single pass of autofixing (e.g. with the `--fix` command line flag). If this is `null`, asserts that none of the reported problems suggest autofixes.
diff --git a/docs/rules/keyword-spacing.md b/docs/rules/keyword-spacing.md
index b8d97a73e..becc9ef32 100644
--- a/docs/rules/keyword-spacing.md
+++ b/docs/rules/keyword-spacing.md
@@ -59,44 +59,44 @@ if (foo) {
     //...
 }
 
-// no conflict with `array-bracket-spacing`
+// Avoid conflict with `array-bracket-spacing`
 let a = [this];
 let b = [function() {}];
 
-// no conflict with `arrow-spacing`
+// Avoid conflict with `arrow-spacing`
 let a = ()=> this.foo;
 
-// no conflict with `block-spacing`
+// Avoid conflict with `block-spacing`
 {function foo() {}}
 
-// no conflict with `comma-spacing`
+// Avoid conflict with `comma-spacing`
 let a = [100,this.foo, this.bar];
 
-// not conflict with `computed-property-spacing`
+// Avoid conflict with `computed-property-spacing`
 obj[this.foo] = 0;
 
-// no conflict with `generator-star-spacing`
+// Avoid conflict with `generator-star-spacing`
 function *foo() {}
 
-// no conflict with `key-spacing`
+// Avoid conflict with `key-spacing`
 let obj = {
     foo:function() {}
 };
 
-// no conflict with `object-curly-spacing`
+// Avoid conflict with `object-curly-spacing`
 let obj = {foo: this};
 
-// no conflict with `semi-spacing`
+// Avoid conflict with `semi-spacing`
 let a = this;function foo() {}
 
-// no conflict with `space-in-parens`
+// Avoid conflict with `space-in-parens`
 (function () {})();
 
-// no conflict with `space-infix-ops`
+// Avoid conflict with `space-infix-ops`
 if ("foo"in {foo: 0}) {}
 if (10+this.foo<= this.bar) {}
 
-// no conflict with `jsx-curly-spacing`
+// Avoid conflict with `jsx-curly-spacing`
 let a = <A foo={this.foo} bar={function(){}} />
 ```
 
@@ -157,57 +157,57 @@ if (foo) {
     //...
 }
 
-// not conflict with `array-bracket-spacing`
+// Avoid conflict with `array-bracket-spacing`
 let a = [this];
 
-// not conflict with `arrow-spacing`
+// Avoid conflict with `arrow-spacing`
 let a = ()=> this.foo;
 
-// not conflict with `comma-spacing`
+// Avoid conflict with `comma-spacing`
 let a = [100, this.foo, this.bar];
 
-// not conflict with `computed-property-spacing`
+// Avoid conflict with `computed-property-spacing`
 obj[this.foo] = 0;
 
-// not conflict with `generator-star-spacing`
+// Avoid conflict with `generator-star-spacing`
 function* foo() {}
 
-// not conflict with `key-spacing`
+// Avoid conflict with `key-spacing`
 let obj = {
     foo:function() {}
 };
 
-// not conflict with `func-call-spacing`
+// Avoid conflict with `func-call-spacing`
 class A {
     constructor() {
         super();
     }
 }
 
-// not conflict with `object-curly-spacing`
+// Avoid conflict with `object-curly-spacing`
 let obj = {foo: this};
 
-// not conflict with `semi-spacing`
+// Avoid conflict with `semi-spacing`
 let a = this;function foo() {}
 
-// not conflict with `space-before-function-paren`
+// Avoid conflict with `space-before-function-paren`
 function() {}
 
-// no conflict with `space-infix-ops`
+// Avoid conflict with `space-infix-ops`
 if ("foo"in{foo: 0}) {}
 if (10+this.foo<= this.bar) {}
 
-// no conflict with `space-unary-ops`
+// Avoid conflict with `space-unary-ops`
 function* foo(a) {
     return yield+a;
 }
 
-// no conflict with `yield-star-spacing`
+// Avoid conflict with `yield-star-spacing`
 function* foo(a) {
     return yield* a;
 }
 
-// no conflict with `jsx-curly-spacing`
+// Avoid conflict with `jsx-curly-spacing`
 let a = <A foo={this.foo} bar={function(){}} />
 ```
 
diff --git a/lib/config/config-validator.js b/lib/config/config-validator.js
index 3d98b5104..1a5b3ef13 100644
--- a/lib/config/config-validator.js
+++ b/lib/config/config-validator.js
@@ -98,7 +98,8 @@ function validateRuleSchema(rule, localOptions) {
  * @param {{create: Function}|null} rule The rule that the config is being validated for
  * @param {string} ruleId The rule's unique name.
  * @param {array|number} options The given options for the rule.
- * @param {string} source The name of the configuration source to report in any errors.
+ * @param {string|null} source The name of the configuration source to report in any errors. If null or undefined,
+ * no source is prepended to the message.
  * @returns {void}
  */
 function validateRuleOptions(rule, ruleId, options, source) {
@@ -112,7 +113,13 @@ function validateRuleOptions(rule, ruleId, options, source) {
             validateRuleSchema(rule, Array.isArray(options) ? options.slice(1) : []);
         }
     } catch (err) {
-        throw new Error(`${source}:\n\tConfiguration for rule "${ruleId}" is invalid:\n${err.message}`);
+        const enhancedMessage = `Configuration for rule "${ruleId}" is invalid:\n${err.message}`;
+
+        if (typeof source === "string") {
+            throw new Error(`${source}:\n\t${enhancedMessage}`);
+        } else {
+            throw new Error(enhancedMessage);
+        }
     }
 }
 
diff --git a/lib/linter.js b/lib/linter.js
index 2238c5be0..6cbb25b1c 100755
--- a/lib/linter.js
+++ b/lib/linter.js
@@ -340,7 +340,21 @@ function modifyConfigsFromComments(filename, ast, config, ruleMapper) {
                             Object.keys(parseResult.config).forEach(name => {
                                 const ruleValue = parseResult.config[name];
 
-                                validator.validateRuleOptions(ruleMapper(name), name, ruleValue, `${filename} line ${comment.loc.start.line}`);
+                                try {
+                                    validator.validateRuleOptions(ruleMapper(name), name, ruleValue);
+                                } catch (err) {
+                                    problems.push({
+                                        ruleId: name,
+                                        severity: 2,
+                                        source: null,
+                                        message: err.message,
+                                        line: comment.loc.start.line,
+                                        column: comment.loc.start.column + 1,
+                                        endLine: comment.loc.end.line,
+                                        endColumn: comment.loc.end.column + 1,
+                                        nodeType: null
+                                    });
+                                }
                                 commentRules[name] = ruleValue;
                             });
                         } else {
diff --git a/lib/rules/indent.js b/lib/rules/indent.js
index 2159880f8..79a0f25ca 100644
--- a/lib/rules/indent.js
+++ b/lib/rules/indent.js
@@ -442,6 +442,7 @@ class OffsetStorage {
                 const offset = (
                     offsetInfo.from &&
                     offsetInfo.from.loc.start.line === token.loc.start.line &&
+                    !/^\s*?\n/.test(token.value) &&
                     !offsetInfo.force
                 ) ? 0 : offsetInfo.offset * this._indentSize;
 
@@ -836,7 +837,7 @@ module.exports = {
                     const previousElement = elements[index - 1];
                     const firstTokenOfPreviousElement = previousElement && getFirstToken(previousElement);
 
-                    if (previousElement && sourceCode.getLastToken(previousElement).loc.start.line > startToken.loc.end.line) {
+                    if (previousElement && sourceCode.getLastToken(previousElement).loc.end.line > startToken.loc.end.line) {
                         offsets.setDesiredOffsets(element.range, firstTokenOfPreviousElement, 0);
                     }
                 }
@@ -1094,8 +1095,8 @@ module.exports = {
                     const questionMarkToken = sourceCode.getFirstTokenBetween(node.test, node.consequent, token => token.type === "Punctuator" && token.value === "?");
                     const colonToken = sourceCode.getFirstTokenBetween(node.consequent, node.alternate, token => token.type === "Punctuator" && token.value === ":");
 
-                    const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken, { includeComments: true });
-                    const lastConsequentToken = sourceCode.getTokenBefore(colonToken, { includeComments: true });
+                    const firstConsequentToken = sourceCode.getTokenAfter(questionMarkToken);
+                    const lastConsequentToken = sourceCode.getTokenBefore(colonToken);
                     const firstAlternateToken = sourceCode.getTokenAfter(colonToken);
 
                     offsets.setDesiredOffset(questionMarkToken, firstToken, 1);
diff --git a/lib/rules/prefer-destructuring.js b/lib/rules/prefer-destructuring.js
index e9d02da3a..112ea6461 100644
--- a/lib/rules/prefer-destructuring.js
+++ b/lib/rules/prefer-destructuring.js
@@ -165,8 +165,10 @@ module.exports = {
             if (shouldCheck(reportNode.type, "object")) {
                 const property = rightNode.property;
 
-                if ((property.type === "Literal" && leftNode.name === property.value) || (property.type === "Identifier" &&
-                    leftNode.name === property.name)) {
+                if (
+                    (property.type === "Literal" && leftNode.name === property.value) ||
+                    (property.type === "Identifier" && leftNode.name === property.name && !rightNode.computed)
+                ) {
                     report(reportNode, "object");
                 }
             }
diff --git a/lib/rules/valid-jsdoc.js b/lib/rules/valid-jsdoc.js
index 5e1af10de..c213c50f1 100644
--- a/lib/rules/valid-jsdoc.js
+++ b/lib/rules/valid-jsdoc.js
@@ -57,7 +57,9 @@ module.exports = {
                 },
                 additionalProperties: false
             }
-        ]
+        ],
+
+        fixable: "code"
     },
 
     create(context) {
@@ -145,23 +147,35 @@ module.exports = {
         /**
          * Extract the current and expected type based on the input type object
          * @param {Object} type JSDoc tag
-         * @returns {Object} current and expected type object
+         * @returns {{currentType: Doctrine.Type, expectedTypeName: string}} The current type annotation and
+         * the expected name of the annotation
          * @private
          */
         function getCurrentExpectedTypes(type) {
             let currentType;
 
             if (type.name) {
-                currentType = type.name;
+                currentType = type;
             } else if (type.expression) {
-                currentType = type.expression.name;
+                currentType = type.expression;
             }
 
-            const expectedType = currentType && preferType[currentType];
-
             return {
                 currentType,
-                expectedType
+                expectedTypeName: currentType && preferType[currentType.name]
+            };
+        }
+
+        /**
+         * Gets the location of a JSDoc node in a file
+         * @param {Token} jsdocComment The comment that this node is parsed from
+         * @param {{range: number[]}} parsedJsdocNode A tag or other node which was parsed from this comment
+         * @returns {{start: SourceLocation, end: SourceLocation}} The 0-based source location for the tag
+         */
+        function getAbsoluteRange(jsdocComment, parsedJsdocNode) {
+            return {
+                start: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[0]),
+                end: sourceCode.getLocFromIndex(jsdocComment.range[0] + 2 + parsedJsdocNode.range[1])
             };
         }
 
@@ -204,14 +218,21 @@ module.exports = {
             elements.forEach(validateType.bind(null, jsdocNode));
 
             typesToCheck.forEach(typeToCheck => {
-                if (typeToCheck.expectedType &&
-                    typeToCheck.expectedType !== typeToCheck.currentType) {
+                if (typeToCheck.expectedTypeName &&
+                    typeToCheck.expectedTypeName !== typeToCheck.currentType.name) {
                     context.report({
                         node: jsdocNode,
-                        message: "Use '{{expectedType}}' instead of '{{currentType}}'.",
+                        message: "Use '{{expectedTypeName}}' instead of '{{currentTypeName}}'.",
+                        loc: getAbsoluteRange(jsdocNode, typeToCheck.currentType),
                         data: {
-                            currentType: typeToCheck.currentType,
-                            expectedType: typeToCheck.expectedType
+                            currentTypeName: typeToCheck.currentType.name,
+                            expectedTypeName: typeToCheck.expectedTypeName
+                        },
+                        fix(fixer) {
+                            return fixer.replaceTextRange(
+                                typeToCheck.currentType.range.map(indexInComment => jsdocNode.range[0] + 2 + indexInComment),
+                                typeToCheck.expectedTypeName
+                            );
                         }
                     });
                 }
@@ -227,8 +248,8 @@ module.exports = {
         function checkJSDoc(node) {
             const jsdocNode = sourceCode.getJSDocComment(node),
                 functionData = fns.pop(),
-                params = Object.create(null),
-                paramsTags = [];
+                paramTagsByName = Object.create(null),
+                paramTags = [];
             let hasReturns = false,
                 returnsTag,
                 hasConstructor = false,
@@ -244,7 +265,8 @@ module.exports = {
                     jsdoc = doctrine.parse(jsdocNode.value, {
                         strict: true,
                         unwrap: true,
-                        sloppy: true
+                        sloppy: true,
+                        range: true
                     });
                 } catch (ex) {
 
@@ -264,7 +286,7 @@ module.exports = {
                         case "param":
                         case "arg":
                         case "argument":
-                            paramsTags.push(tag);
+                            paramTags.push(tag);
                             break;
 
                         case "return":
@@ -297,7 +319,29 @@ module.exports = {
 
                     // check tag preferences
                     if (prefer.hasOwnProperty(tag.title) && tag.title !== prefer[tag.title]) {
-                        context.report({ node: jsdocNode, message: "Use @{{name}} instead.", data: { name: prefer[tag.title] } });
+                        const entireTagRange = getAbsoluteRange(jsdocNode, tag);
+
+                        context.report({
+                            node: jsdocNode,
+                            message: "Use @{{name}} instead.",
+                            loc: {
+                                start: entireTagRange.start,
+                                end: {
+                                    line: entireTagRange.start.line,
+                                    column: entireTagRange.start.column + `@${tag.title}`.length
+                                }
+                            },
+                            data: { name: prefer[tag.title] },
+                            fix(fixer) {
+                                return fixer.replaceTextRange(
+                                    [
+                                        jsdocNode.range[0] + tag.range[0] + 3,
+                                        jsdocNode.range[0] + tag.range[0] + tag.title.length + 3
+                                    ],
+                                    prefer[tag.title]
+                                );
+                            }
+                        });
                     }
 
                     // validate the types
@@ -306,17 +350,32 @@ module.exports = {
                     }
                 });
 
-                paramsTags.forEach(param => {
+                paramTags.forEach(param => {
                     if (!param.type) {
-                        context.report({ node: jsdocNode, message: "Missing JSDoc parameter type for '{{name}}'.", data: { name: param.name } });
+                        context.report({
+                            node: jsdocNode,
+                            message: "Missing JSDoc parameter type for '{{name}}'.",
+                            loc: getAbsoluteRange(jsdocNode, param),
+                            data: { name: param.name }
+                        });
                     }
                     if (!param.description && requireParamDescription) {
-                        context.report({ node: jsdocNode, message: "Missing JSDoc parameter description for '{{name}}'.", data: { name: param.name } });
+                        context.report({
+                            node: jsdocNode,
+                            message: "Missing JSDoc parameter description for '{{name}}'.",
+                            loc: getAbsoluteRange(jsdocNode, param),
+                            data: { name: param.name }
+                        });
                     }
-                    if (params[param.name]) {
-                        context.report({ node: jsdocNode, message: "Duplicate JSDoc parameter '{{name}}'.", data: { name: param.name } });
+                    if (paramTagsByName[param.name]) {
+                        context.report({
+                            node: jsdocNode,
+                            message: "Duplicate JSDoc parameter '{{name}}'.",
+                            loc: getAbsoluteRange(jsdocNode, param),
+                            data: { name: param.name }
+                        });
                     } else if (param.name.indexOf(".") === -1) {
-                        params[param.name] = 1;
+                        paramTagsByName[param.name] = param;
                     }
                 });
 
@@ -325,6 +384,7 @@ module.exports = {
                         context.report({
                             node: jsdocNode,
                             message: "Unexpected @{{title}} tag; function has no return statement.",
+                            loc: getAbsoluteRange(jsdocNode, returnsTag),
                             data: {
                                 title: returnsTag.title
                             }
@@ -356,10 +416,10 @@ module.exports = {
                 }
 
                 // check the parameters
-                const jsdocParams = Object.keys(params);
+                const jsdocParamNames = Object.keys(paramTagsByName);
 
                 if (node.params) {
-                    node.params.forEach((param, i) => {
+                    node.params.forEach((param, paramsIndex) => {
                         if (param.type === "AssignmentPattern") {
                             param = param.left;
                         }
@@ -368,16 +428,17 @@ module.exports = {
 
                         // TODO(nzakas): Figure out logical things to do with destructured, default, rest params
                         if (param.type === "Identifier") {
-                            if (jsdocParams[i] && (name !== jsdocParams[i])) {
+                            if (jsdocParamNames[paramsIndex] && (name !== jsdocParamNames[paramsIndex])) {
                                 context.report({
                                     node: jsdocNode,
                                     message: "Expected JSDoc for '{{name}}' but found '{{jsdocName}}'.",
+                                    loc: getAbsoluteRange(jsdocNode, paramTagsByName[jsdocParamNames[paramsIndex]]),
                                     data: {
                                         name,
-                                        jsdocName: jsdocParams[i]
+                                        jsdocName: jsdocParamNames[paramsIndex]
                                     }
                                 });
-                            } else if (!params[name] && !isOverride) {
+                            } else if (!paramTagsByName[name] && !isOverride) {
                                 context.report({
                                     node: jsdocNode,
                                     message: "Missing JSDoc for parameter '{{name}}'.",
diff --git a/package.json b/package.json
index ae3bce395..286e52a63 100644
--- a/package.json
+++ b/package.json
@@ -40,7 +40,7 @@
     "concat-stream": "^1.6.0",
     "cross-spawn": "^5.1.0",
     "debug": "^3.1.0",
-    "doctrine": "^2.0.2",
+    "doctrine": "^2.1.0",
     "eslint-scope": "^3.7.1",
     "eslint-visitor-keys": "^1.0.0",
     "espree": "^3.5.2",


Test patch:
diff --git a/tests/lib/linter.js b/tests/lib/linter.js
index 481a0846f..fc4298d56 100644
--- a/tests/lib/linter.js
+++ b/tests/lib/linter.js
@@ -1615,14 +1615,42 @@ describe("Linter", () => {
     });
 
     describe("when evaluating code with invalid comments to enable rules", () => {
-        const code = "/*eslint no-alert:true*/ alert('test');";
-
-        it("should report a violation", () => {
-            const config = { rules: {} };
-
-            const fn = linter.verify.bind(linter, code, config, filename);
+        it("should report a violation when the config is not a valid rule configuration", () => {
+            assert.deepStrictEqual(
+                linter.verify("/*eslint no-alert:true*/ alert('test');", {}),
+                [
+                    {
+                        severity: 2,
+                        ruleId: "no-alert",
+                        message: "Configuration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n",
+                        line: 1,
+                        column: 1,
+                        endLine: 1,
+                        endColumn: 25,
+                        source: null,
+                        nodeType: null
+                    }
+                ]
+            );
+        });
 
-            assert.throws(fn, "filename.js line 1:\n\tConfiguration for rule \"no-alert\" is invalid:\n\tSeverity should be one of the following: 0 = off, 1 = warn, 2 = error (you passed 'true').\n");
+        it("should report a violation when the config violates a rule's schema", () => {
+            assert.deepStrictEqual(
+                linter.verify("/* eslint no-alert: [error, {nonExistentPropertyName: true}]*/", {}),
+                [
+                    {
+                        severity: 2,
+                        ruleId: "no-alert",
+                        message: "Configuration for rule \"no-alert\" is invalid:\n\tValue [{\"nonExistentPropertyName\":true}] should NOT have more than 0 items.\n",
+                        line: 1,
+                        column: 1,
+                        endLine: 1,
+                        endColumn: 63,
+                        source: null,
+                        nodeType: null
+                    }
+                ]
+            );
         });
     });
 
diff --git a/tests/lib/rules/indent.js b/tests/lib/rules/indent.js
index 4ec4975bc..6ffa72ce4 100644
--- a/tests/lib/rules/indent.js
+++ b/tests/lib/rules/indent.js
@@ -2830,6 +2830,13 @@ ruleTester.run("indent", rule, {
             `,
             options: [2]
         },
+        unIndent`
+            foo(\`
+                bar
+            \`, {
+                baz: 1
+            });
+        `,
         unIndent`
             function foo() {
                 \`foo\${bar}baz\${
@@ -3856,21 +3863,20 @@ ruleTester.run("indent", rule, {
         unIndent`
             foo(\`foo
                     \`, {
-                    ok: true
-                },
-                {
-                    ok: false
-                }
-            )
+                ok: true
+            },
+            {
+                ok: false
+            })
         `,
         unIndent`
             foo(tag\`foo
                     \`, {
-                    ok: true
-                },
-                {
-                    ok: false
-                }
+                ok: true
+            },
+            {
+                ok: false
+            }
             )
         `,
 
@@ -4796,7 +4802,16 @@ ruleTester.run("indent", rule, {
                 }
             `,
             options: [4, { ignoreComments: true }]
-        }
+        },
+        unIndent`
+            const obj = {
+                foo () {
+                    return condition ? // comment
+                        1 :
+                        2
+                }
+            }
+        `
     ],
 
     invalid: [
@@ -9261,6 +9276,27 @@ ruleTester.run("indent", rule, {
             `,
             options: [4, { ignoreComments: false }],
             errors: expectedErrors([4, 4, 0, "Block"])
+        },
+        {
+            code: unIndent`
+                const obj = {
+                    foo () {
+                        return condition ? // comment
+                        1 :
+                            2
+                    }
+                }
+            `,
+            output: unIndent`
+                const obj = {
+                    foo () {
+                        return condition ? // comment
+                            1 :
+                            2
+                    }
+                }
+            `,
+            errors: expectedErrors([4, 12, 8, "Numeric"])
         }
     ]
 });
diff --git a/tests/lib/rules/prefer-destructuring.js b/tests/lib/rules/prefer-destructuring.js
index e60535e7e..a0e4aa79b 100644
--- a/tests/lib/rules/prefer-destructuring.js
+++ b/tests/lib/rules/prefer-destructuring.js
@@ -131,7 +131,9 @@ ruleTester.run("prefer-destructuring", rule, {
             code: "var foo = object.foo;",
             options: [{ VariableDeclarator: { object: false }, AssignmentExpression: { object: true } }]
         },
-        "class Foo extends Bar { static foo() {var foo = super.foo} }"
+        "class Foo extends Bar { static foo() {var foo = super.foo} }",
+        "foo = bar[foo];",
+        "var foo = bar[foo];"
     ],
 
     invalid: [
diff --git a/tests/lib/rules/valid-jsdoc.js b/tests/lib/rules/valid-jsdoc.js
index 54fea8d8f..d6e0219c5 100644
--- a/tests/lib/rules/valid-jsdoc.js
+++ b/tests/lib/rules/valid-jsdoc.js
@@ -1044,14 +1044,20 @@ ruleTester.run("valid-jsdoc", rule, {
                 "    return 'the return';\n" +
                 "  }\n" +
                 ");\n",
+            output: null,
             options: [{ requireReturn: false }],
             errors: [{
                 message: "Expected JSDoc for 'argName' but found 'bogusName'.",
-                type: "Block"
+                type: "Block",
+                line: 4,
+                column: 6,
+                endLine: 4,
+                endColumn: 62
             }]
         },
         {
             code: "/** @@foo */\nfunction foo(){}",
+            output: null,
             errors: [{
                 message: "JSDoc syntax error.",
                 type: "Block"
@@ -1070,6 +1076,7 @@ ruleTester.run("valid-jsdoc", rule, {
                 "    return 'bar';\n" +
                 "  }\n" +
                 "});\n",
+            output: null,
             options: [{ requireReturn: false }],
             errors: [{
                 message: "Missing JSDoc @returns for function.",
@@ -1078,6 +1085,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** @@returns {void} Foo */\nfunction foo(){}",
+            output: null,
             errors: [{
                 message: "JSDoc syntax error.",
                 type: "Block"
@@ -1085,6 +1093,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo \n@returns {void Foo\n */\nfunction foo(){}",
+            output: null,
             errors: [{
                 message: "JSDoc type missing brace.",
                 type: "Block"
@@ -1092,25 +1101,36 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo \n@return {void} Foo\n */\nfunction foo(){}",
+            output: "/** Foo \n@returns {void} Foo\n */\nfunction foo(){}",
             options: [{ prefer: { return: "returns" } }],
             errors: [{
                 message: "Use @returns instead.",
-                type: "Block"
+                type: "Block",
+                line: 2,
+                column: 1,
+                endLine: 2,
+                endColumn: 8
             }]
         },
         {
             code: "/** Foo \n@argument {int} bar baz\n */\nfunction foo(bar){}",
+            output: "/** Foo \n@arg {int} bar baz\n */\nfunction foo(bar){}",
             options: [{ prefer: { argument: "arg" } }],
             errors: [{
-                message: "Use @arg instead.",
-                type: "Block"
-            }, {
                 message: "Missing JSDoc @returns for function.",
                 type: "Block"
+            }, {
+                message: "Use @arg instead.",
+                type: "Block",
+                line: 2,
+                column: 1,
+                endLine: 2,
+                endColumn: 10
             }]
         },
         {
             code: "/** Foo \n */\nfunction foo(){}",
+            output: null,
             options: [{ prefer: { returns: "return" } }],
             errors: [{
                 message: "Missing JSDoc @return for function.",
@@ -1119,15 +1139,21 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo \n@return {void} Foo\n */\nfoo.bar = () => {}",
+            output: "/** Foo \n@returns {void} Foo\n */\nfoo.bar = () => {}",
             options: [{ prefer: { return: "returns" } }],
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 message: "Use @returns instead.",
-                type: "Block"
+                type: "Block",
+                line: 2,
+                column: 1,
+                endLine: 2,
+                endColumn: 8
             }]
         },
         {
             code: "/** Foo \n@param {void Foo\n */\nfunction foo(){}",
+            output: null,
             errors: [{
                 message: "JSDoc type missing brace.",
                 type: "Block"
@@ -1135,6 +1161,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo \n@param {} p Bar\n */\nfunction foo(){}",
+            output: null,
             errors: [{
                 message: "JSDoc syntax error.",
                 type: "Block"
@@ -1142,6 +1169,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo \n@param {void Foo */\nfunction foo(){}",
+            output: null,
             errors: [{
                 message: "JSDoc type missing brace.",
                 type: "Block"
@@ -1149,42 +1177,62 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo\n* @param p Desc \n*/\nfunction foo(){}",
+            output: null,
             errors: [{
-                message: "Missing JSDoc parameter type for 'p'.",
-                type: "Block"
-            }, {
                 message: "Missing JSDoc @returns for function.",
                 type: "Block"
+            }, {
+                message: "Missing JSDoc parameter type for 'p'.",
+                type: "Block",
+                line: 2,
+                column: 3,
+                endLine: 2,
+                endColumn: 16
             }]
         },
         {
             code: "/**\n* Foo\n* @param {string} p \n*/\nfunction foo(){}",
+            output: null,
             errors: [{
-                message: "Missing JSDoc parameter description for 'p'.",
-                type: "Block"
-            }, {
                 message: "Missing JSDoc @returns for function.",
                 type: "Block"
+            }, {
+                message: "Missing JSDoc parameter description for 'p'.",
+                type: "Block",
+                line: 3,
+                column: 3,
+                endLine: 3,
+                endColumn: 20
             }]
         },
         {
             code: "/**\n* Foo\n* @param {string} p \n*/\nvar foo = function(){}",
+            output: null,
             errors: [{
-                message: "Missing JSDoc parameter description for 'p'.",
-                type: "Block"
-            }, {
                 message: "Missing JSDoc @returns for function.",
                 type: "Block"
+            }, {
+                message: "Missing JSDoc parameter description for 'p'.",
+                type: "Block",
+                line: 3,
+                column: 3,
+                endLine: 3,
+                endColumn: 20
             }]
         },
         {
             code: "/**\n* Foo\n* @param {string} p \n*/\nvar foo = \nfunction(){}",
+            output: null,
             errors: [{
-                message: "Missing JSDoc parameter description for 'p'.",
-                type: "Block"
-            }, {
                 message: "Missing JSDoc @returns for function.",
                 type: "Block"
+            }, {
+                message: "Missing JSDoc parameter description for 'p'.",
+                type: "Block",
+                line: 3,
+                column: 3,
+                endLine: 3,
+                endColumn: 20
             }]
         },
         {
@@ -1202,6 +1250,7 @@ ruleTester.run("valid-jsdoc", rule, {
             "        this.a = xs;" +
             "    }\n" +
             "};",
+            output: null,
             options: [{
                 requireReturn: true,
                 matchDescription: "^[A-Z][A-Za-z0-9\\s]*[.]$"
@@ -1222,6 +1271,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/**\n* Foo\n* @returns {string} \n*/\nfunction foo(){}",
+            output: null,
             errors: [{
                 message: "Missing JSDoc return description.",
                 type: "Block"
@@ -1229,6 +1279,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/**\n* Foo\n* @returns {string} something \n*/\nfunction foo(p){}",
+            output: null,
             errors: [{
                 message: "Missing JSDoc for parameter 'p'.",
                 type: "Block"
@@ -1236,6 +1287,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/**\n* Foo\n* @returns {string} something \n*/\nvar foo = \nfunction foo(a = 1){}",
+            output: null,
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 message: "Missing JSDoc for parameter 'a'.",
@@ -1244,49 +1296,79 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/**\n* Foo\n* @param {string} a Description \n* @param {string} b Description \n* @returns {string} something \n*/\nvar foo = \nfunction foo(b, a = 1){}",
+            output: null,
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 message: "Expected JSDoc for 'b' but found 'a'.",
-                type: "Block"
+                type: "Block",
+                line: 3,
+                column: 3,
+                endLine: 3,
+                endColumn: 32
             },
             {
                 message: "Expected JSDoc for 'a' but found 'b'.",
-                type: "Block"
+                type: "Block",
+                line: 4,
+                column: 3,
+                endLine: 4,
+                endColumn: 32
             }]
         },
         {
             code: "/**\n* Foo\n* @param {string} p desc\n* @param {string} p desc \n*/\nfunction foo(){}",
+            output: null,
             errors: [{
-                message: "Duplicate JSDoc parameter 'p'.",
-                type: "Block"
-            }, {
                 message: "Missing JSDoc @returns for function.",
                 type: "Block"
+            }, {
+                message: "Duplicate JSDoc parameter 'p'.",
+                type: "Block",
+                line: 4,
+                column: 3,
+                endLine: 4,
+                endColumn: 25
             }]
         },
         {
             code: "/**\n* Foo\n* @param {string} a desc\n@returns {void}*/\nfunction foo(b){}",
+            output: null,
             errors: [{
                 message: "Expected JSDoc for 'b' but found 'a'.",
-                type: "Block"
+                type: "Block",
+                line: 3,
+                column: 3,
+                endLine: 3,
+                endColumn: 25
             }]
         },
         {
             code: "/**\n* Foo\n* @override\n* @param {string} a desc\n */\nfunction foo(b){}",
+            output: null,
             errors: [{
                 message: "Expected JSDoc for 'b' but found 'a'.",
-                type: "Block"
+                type: "Block",
+                line: 4,
+                column: 3,
+                endLine: 4,
+                endColumn: 25
             }]
         },
         {
             code: "/**\n* Foo\n* @inheritdoc\n* @param {string} a desc\n */\nfunction foo(b){}",
+            output: null,
             errors: [{
                 message: "Expected JSDoc for 'b' but found 'a'.",
-                type: "Block"
+                type: "Block",
+                line: 4,
+                column: 3,
+                endLine: 4,
+                endColumn: 25
             }]
         },
         {
             code: "/**\n* Foo\n* @param {string} a desc\n*/\nfunction foo(a){var t = false; if(t) {return t;}}",
+            output: null,
             options: [{ requireReturn: false }],
             errors: [{
                 message: "Missing JSDoc @returns for function.",
@@ -1295,6 +1377,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/**\n* Foo\n* @param {string} a desc\n*/\nfunction foo(a){var t = false; if(t) {return null;}}",
+            output: null,
             options: [{ requireReturn: false }],
             errors: [{
                 message: "Missing JSDoc @returns for function.",
@@ -1303,32 +1386,48 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/**\n* Foo\n* @param {string} a desc\n@returns {MyClass}*/\nfunction foo(a){var t = false; if(t) {process(t);}}",
+            output: null,
             options: [{ requireReturn: false }],
             errors: [{
                 message: "Unexpected @returns tag; function has no return statement.",
-                type: "Block"
+                type: "Block",
+                line: 4,
+                column: 1,
+                endLine: 4,
+                endColumn: 19
             }]
         },
         {
             code: "/**\n * Does something. \n* @param {string} a - this is a \n* @return {Array<number>} The result of doing it \n*/\n export function doSomething(a) { }",
+            output: "/**\n * Does something. \n* @param {string} a - this is a \n* @returns {Array<number>} The result of doing it \n*/\n export function doSomething(a) { }",
             options: [{ prefer: { return: "returns" } }],
             parserOptions: { sourceType: "module" },
             errors: [{
                 message: "Use @returns instead.",
-                type: "Block"
+                type: "Block",
+                line: 4,
+                column: 3,
+                endLine: 4,
+                endColumn: 10
             }]
         },
         {
             code: "/**\n * Does something. \n* @param {string} a - this is a \n* @return {Array<number>} The result of doing it \n*/\n export default function doSomething(a) { }",
+            output: "/**\n * Does something. \n* @param {string} a - this is a \n* @returns {Array<number>} The result of doing it \n*/\n export default function doSomething(a) { }",
             options: [{ prefer: { return: "returns" } }],
             parserOptions: { sourceType: "module" },
             errors: [{
                 message: "Use @returns instead.",
-                type: "Block"
+                type: "Block",
+                line: 4,
+                column: 3,
+                endLine: 4,
+                endColumn: 10
             }]
         },
         {
             code: "/** foo */ var foo = () => bar();",
+            output: null,
             options: [{ requireReturn: false }],
             parserOptions: { ecmaVersion: 6 },
             errors: [{
@@ -1338,6 +1437,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** foo */ var foo = () => { return bar(); };",
+            output: null,
             options: [{ requireReturn: false }],
             parserOptions: { ecmaVersion: 6 },
             errors: [{
@@ -1347,28 +1447,39 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** @returns {object} foo */ var foo = () => { bar(); };",
+            output: null,
             options: [{ requireReturn: false }],
             parserOptions: { ecmaVersion: 6 },
             errors: [{
                 message: "Unexpected @returns tag; function has no return statement.",
-                type: "Block"
+                type: "Block",
+                line: 1,
+                column: 5,
+                endLine: 1,
+                endColumn: 26
             }]
         },
         {
             code: "/**\n* @param fields [Array]\n */\n function foo(){}",
+            output: null,
             errors: [
                 {
-                    message: "Missing JSDoc parameter type for 'fields'.",
+                    message: "Missing JSDoc @returns for function.",
                     type: "Block"
                 },
                 {
-                    message: "Missing JSDoc @returns for function.",
-                    type: "Block"
+                    message: "Missing JSDoc parameter type for 'fields'.",
+                    type: "Block",
+                    line: 2,
+                    column: 3,
+                    endLine: 2,
+                    endColumn: 24
                 }
             ]
         },
         {
             code: "/**\n* Start with caps and end with period\n* @return {void} */\nfunction foo(){}",
+            output: null,
             options: [{
                 matchDescription: "^[A-Z][A-Za-z0-9\\s]*[.]$"
             }],
@@ -1379,6 +1490,7 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo \n@return Foo\n */\nfunction foo(){}",
+            output: null,
             options: [{ prefer: { return: "return" } }],
             errors: [{
                 message: "Missing JSDoc return type.",
@@ -1387,13 +1499,18 @@ ruleTester.run("valid-jsdoc", rule, {
         },
         {
             code: "/** Foo \n@return sdf\n */\nfunction foo(){}",
+            output: null,
             options: [{
                 prefer: { return: "return" },
                 requireReturn: false
             }],
             errors: [{
                 message: "Unexpected @return tag; function has no return statement.",
-                type: "Block"
+                type: "Block",
+                line: 2,
+                column: 1,
+                endLine: 2,
+                endColumn: 12
             }]
         },
 
@@ -1412,6 +1529,7 @@ ruleTester.run("valid-jsdoc", rule, {
                 "        this.a = xs;" +
                 "    }\n" +
                 "}",
+            output: null,
             options: [{
                 requireReturn: false,
                 matchDescription: "^[A-Z][A-Za-z0-9\\s]*[.]$"
@@ -1444,6 +1562,7 @@ ruleTester.run("valid-jsdoc", rule, {
                 "        this.a = xs;" +
                 "    }\n" +
                 "};",
+            output: null,
             options: [{
                 requireReturn: true,
                 matchDescription: "^[A-Z][A-Za-z0-9\\s]*[.]$"
@@ -1483,6 +1602,7 @@ ruleTester.run("valid-jsdoc", rule, {
                 "        this.a = xs;" +
                 "    }\n" +
                 "}",
+            output: null,
             options: [],
             parserOptions: {
                 ecmaVersion: 6
@@ -1505,6 +1625,7 @@ ruleTester.run("valid-jsdoc", rule, {
                 " * @this {not.a.valid.type.expression\n" +
                 " */\n" +
                 "function foo() {}",
+            output: null,
             options: [{ requireReturn: false }],
             errors: [{
                 message: "JSDoc type missing brace.",
@@ -1518,6 +1639,7 @@ ruleTester.run("valid-jsdoc", rule, {
                 " * @this {Array<string>}\n" +
                 " */\n" +
                 "function foo() {}",
+            output: null,
             options: [{ requireReturn: false }],
             errors: [{
                 message: "JSDoc syntax error.",
@@ -1534,6 +1656,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {Astnode} returns a node\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {string} hi - desc\n" +
+            "* @returns {ASTNode} returns a node\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     String: "string",
@@ -1543,11 +1672,19 @@ ruleTester.run("valid-jsdoc", rule, {
             errors: [
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 11,
+                    endLine: 3,
+                    endColumn: 17
                 },
                 {
                     message: "Use 'ASTNode' instead of 'Astnode'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 4,
+                    column: 13,
+                    endLine: 4,
+                    endColumn: 20
                 }
             ]
         },
@@ -1559,6 +1696,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {Astnode} returns a node\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {{20:string}} hi - desc\n" +
+            "* @returns {ASTNode} returns a node\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     String: "string",
@@ -1568,11 +1712,19 @@ ruleTester.run("valid-jsdoc", rule, {
             errors: [
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 15,
+                    endLine: 3,
+                    endColumn: 21
                 },
                 {
                     message: "Use 'ASTNode' instead of 'Astnode'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 4,
+                    column: 13,
+                    endLine: 4,
+                    endColumn: 20
                 }
             ]
         },
@@ -1584,6 +1736,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {Astnode} returns a node\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {String|number|Test} hi - desc\n" +
+            "* @returns {Astnode} returns a node\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     test: "Test"
@@ -1592,7 +1751,11 @@ ruleTester.run("valid-jsdoc", rule, {
             errors: [
                 {
                     message: "Use 'Test' instead of 'test'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 25,
+                    endLine: 3,
+                    endColumn: 29
                 }
             ]
         },
@@ -1604,6 +1767,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {Astnode} returns a node\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {Array.<string>} hi - desc\n" +
+            "* @returns {Astnode} returns a node\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     String: "string",
@@ -1613,7 +1783,11 @@ ruleTester.run("valid-jsdoc", rule, {
             errors: [
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 18,
+                    endLine: 3,
+                    endColumn: 24
                 }
             ]
         },
@@ -1625,6 +1799,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {Array.<{summary: String}>} desc\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {Array.<{id: number, votes: number}>} hi - desc\n" +
+            "* @returns {Array.<{summary: string}>} desc\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     Number: "number",
@@ -1634,15 +1815,27 @@ ruleTester.run("valid-jsdoc", rule, {
             errors: [
                 {
                     message: "Use 'number' instead of 'Number'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 23,
+                    endLine: 3,
+                    endColumn: 29
                 },
                 {
                     message: "Use 'number' instead of 'Number'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 38,
+                    endLine: 3,
+                    endColumn: 44
                 },
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 4,
+                    column: 30,
+                    endLine: 4,
+                    endColumn: 36
                 }
             ]
         },
@@ -1654,6 +1847,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {Array.<[String, String]>} desc\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {Array.<[string, number]>} hi - desc\n" +
+            "* @returns {Array.<[string, string]>} desc\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     Number: "number",
@@ -1663,19 +1863,35 @@ ruleTester.run("valid-jsdoc", rule, {
             errors: [
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 19,
+                    endLine: 3,
+                    endColumn: 25
                 },
                 {
                     message: "Use 'number' instead of 'Number'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 27,
+                    endLine: 3,
+                    endColumn: 33
                 },
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 4,
+                    column: 21,
+                    endLine: 4,
+                    endColumn: 27
                 },
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 4,
+                    column: 29,
+                    endLine: 4,
+                    endColumn: 35
                 }
             ]
         },
@@ -1687,6 +1903,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {Boolean} desc\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {Object<string,Object<string, number>>} hi - because why not\n" +
+            "* @returns {Boolean} desc\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     Number: "number",
@@ -1696,24 +1919,44 @@ ruleTester.run("valid-jsdoc", rule, {
             }],
             errors: [
                 {
-                    message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    message: "Use 'Object' instead of 'object'.",
+                    type: "Block",
+                    line: 3,
+                    column: 11,
+                    endLine: 3,
+                    endColumn: 17
                 },
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 18,
+                    endLine: 3,
+                    endColumn: 24
                 },
                 {
-                    message: "Use 'number' instead of 'Number'.",
-                    type: "Block"
+                    message: "Use 'Object' instead of 'object'.",
+                    type: "Block",
+                    line: 3,
+                    column: 25,
+                    endLine: 3,
+                    endColumn: 31
                 },
                 {
-                    message: "Use 'Object' instead of 'object'.",
-                    type: "Block"
+                    message: "Use 'string' instead of 'String'.",
+                    type: "Block",
+                    line: 3,
+                    column: 32,
+                    endLine: 3,
+                    endColumn: 38
                 },
                 {
-                    message: "Use 'Object' instead of 'object'.",
-                    type: "Block"
+                    message: "Use 'number' instead of 'Number'.",
+                    type: "Block",
+                    line: 3,
+                    column: 40,
+                    endLine: 3,
+                    endColumn: 46
                 }
             ]
         },
@@ -1727,6 +1970,13 @@ ruleTester.run("valid-jsdoc", rule, {
             "* @returns {ASTnode} returns a node\n" +
             "*/\n" +
             "function foo(hi){}",
+            output:
+            "/**\n" +
+            "* Foo\n" +
+            "* @param {{foo:string, astnode:Object, bar}} hi - desc\n" +
+            "* @returns {ASTnode} returns a node\n" +
+            "*/\n" +
+            "function foo(hi){}",
             options: [{
                 preferType: {
                     String: "string",
@@ -1736,7 +1986,11 @@ ruleTester.run("valid-jsdoc", rule, {
             errors: [
                 {
                     message: "Use 'string' instead of 'String'.",
-                    type: "Block"
+                    type: "Block",
+                    line: 3,
+                    column: 16,
+                    endLine: 3,
+                    endColumn: 22
                 }
             ]
         }