Repository: nzakas/eslint-plugin-typescript (js)

Issue:
Type annotations not marked as used
Type annotations are not marked as used by typescript/no-unused-vars, so eslint:recommended/no-unused-vars marks this as an error.

```ts
// type literal declared elsewhere
import { Nullable } from '../types';

const str: Nullable<string> = 'hello';
console.log(str);
```

I know there is still a lot of work to do before we can say this rule is working as expected, but I think adding this won't hurt... current work is being tracked in the [typescript-eslint-parser repo](https://github.com/eslint/typescript-eslint-parser/issues/77).




Pull request description:
Fix: type-annotations are marked as used (fixes #61)
Added code to mark type annotations as used.

Patch:
diff --git a/lib/rules/no-unused-vars.js b/lib/rules/no-unused-vars.js
index f10bb1d..39e1f6a 100644
--- a/lib/rules/no-unused-vars.js
+++ b/lib/rules/no-unused-vars.js
@@ -125,10 +125,80 @@ module.exports = {
             });
         }
 
+        /**
+         * Checks if the given node has a type annotation and marks it as used.
+         * @param {ASTNode} node the relevant AST node.
+         * @returns {void}
+         * @private
+         */
+        function markTypeAnnotationAsUsed(node) {
+            const annotation = node.typeAnnotation || node;
+
+            switch (annotation.type) {
+                case "TSTypeReference": {
+                    if (annotation.typeName.type === "TSArrayType") {
+                        markTypeAnnotationAsUsed(
+                            annotation.typeName.elementType
+                        );
+                    } else {
+                        markVariableAsUsed(context, annotation.typeName.name);
+                        if (
+                            annotation.typeParameters &&
+                            annotation.typeParameters.params
+                        ) {
+                            annotation.typeParameters.params.forEach(param => {
+                                markTypeAnnotationAsUsed(param);
+                            });
+                        }
+                    }
+
+                    break;
+                }
+                case "TSUnionType":
+                case "TSIntersectionType":
+                    annotation.types.forEach(type => {
+                        markTypeAnnotationAsUsed(type);
+                    });
+
+                    break;
+
+                default:
+                    break;
+            }
+        }
+
+        /**
+         * Checks if the given node has a return type and marks it as used.
+         * @param {ASTNode} node the relevant AST node.
+         * @returns {void}
+         * @private
+         */
+        function markFunctionReturnTypeAsUsed(node) {
+            if (node.returnType) {
+                markTypeAnnotationAsUsed(node.returnType);
+            }
+        }
+
         //----------------------------------------------------------------------
         // Public
         //----------------------------------------------------------------------
         return {
+            Identifier(node) {
+                if (node.typeAnnotation) {
+                    markTypeAnnotationAsUsed(node.typeAnnotation);
+                }
+            },
+
+            TypeAnnotation(node) {
+                if (node.typeAnnotation) {
+                    markTypeAnnotationAsUsed(node.typeAnnotation);
+                }
+            },
+
+            FunctionDeclaration: markFunctionReturnTypeAsUsed,
+            FunctionExpression: markFunctionReturnTypeAsUsed,
+            ArrowFunctionExpression: markFunctionReturnTypeAsUsed,
+
             ClassProperty: markDecoratorsAsUsed,
             ClassDeclaration(node) {
                 markDecoratorsAsUsed(node);


Test patch:
diff --git a/tests/lib/rules/no-unused-vars.js b/tests/lib/rules/no-unused-vars.js
index caefc56..7dfce8f 100644
--- a/tests/lib/rules/no-unused-vars.js
+++ b/tests/lib/rules/no-unused-vars.js
@@ -175,6 +175,98 @@ ruleTester.run("no-unused-vars", ruleNoUnusedVars, {
                 "new Thing()"
             ].join("\n"),
             parser
+        },
+        {
+            code: [
+                "interface Base {}",
+                "const a: Base = {}",
+                "console.log(a);"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "const a: Nullable<string> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "import { SomeOther } from 'other'",
+                "const a: Nullable<SomeOther> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "const a: Nullable | undefined = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "const a: Nullable & undefined = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "import { SomeOther } from 'other'",
+                "const a: Nullable<SomeOther[]> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "import { SomeOther } from 'other'",
+                "const a: Nullable<Array<SomeOther>> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "const a: Array<Nullable> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "const a: Array<Nullable[]> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "const a: Array<Array<Nullable>> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable'",
+                "import { SomeOther } from 'other'",
+                "const a: Array<Nullable<SomeOther>> = 'hello'",
+                "console.log(a)"
+            ].join("\n"),
+            parser
         }
     ],
 
@@ -193,6 +285,37 @@ ruleTester.run("no-unused-vars", ruleNoUnusedVars, {
                     column: 10
                 }
             ]
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable';",
+                "const a: string = 'hello';",
+                "console.log(a);"
+            ].join("\n"),
+            parser,
+            errors: [
+                {
+                    message: "'Nullable' is defined but never used.",
+                    line: 1,
+                    column: 10
+                }
+            ]
+        },
+        {
+            code: [
+                "import { Nullable } from 'nullable';",
+                "import { SomeOther } from 'other';",
+                "const a: Nullable<string> = 'hello';",
+                "console.log(a);"
+            ].join("\n"),
+            parser,
+            errors: [
+                {
+                    message: "'SomeOther' is defined but never used.",
+                    line: 2,
+                    column: 10
+                }
+            ]
         }
     ]
 });