Skip to content

Commit

Permalink
feat: consume new scope analysis package
Browse files Browse the repository at this point in the history
  • Loading branch information
bradzacher committed Jun 19, 2020
1 parent 25e1f68 commit 07a426a
Show file tree
Hide file tree
Showing 29 changed files with 119 additions and 165,450 deletions.
1 change: 1 addition & 0 deletions packages/eslint-plugin/package.json
Expand Up @@ -43,6 +43,7 @@
},
"dependencies": {
"@typescript-eslint/experimental-utils": "3.3.0",
"@typescript-eslint/scope-manager": "3.3.0",
"debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
Expand Down
87 changes: 49 additions & 38 deletions packages/eslint-plugin/src/rules/no-unused-vars.ts
@@ -1,7 +1,5 @@
import {
AST_NODE_TYPES,
TSESTree,
} from '@typescript-eslint/experimental-utils';
import { TSESTree } from '@typescript-eslint/experimental-utils';
import { PatternVisitor } from '@typescript-eslint/scope-manager';
import baseRule from 'eslint/lib/rules/no-unused-vars';
import * as util from '../util';

Expand All @@ -25,46 +23,51 @@ export default util.createRule<Options, MessageIds>({
create(context) {
const rules = baseRule.create(context);

/**
* Mark heritage clause as used
* @param node The node currently being traversed
*/
function markHeritageAsUsed(node: TSESTree.Expression): void {
switch (node.type) {
case AST_NODE_TYPES.Identifier:
context.markVariableAsUsed(node.name);
break;
case AST_NODE_TYPES.MemberExpression:
markHeritageAsUsed(node.object);
break;
case AST_NODE_TYPES.CallExpression:
markHeritageAsUsed(node.callee);
break;
}
}

return Object.assign({}, rules, {
'TSTypeReference Identifier'(node: TSESTree.Identifier) {
context.markVariableAsUsed(node.name);
return {
...rules,
TSMappedType(node): void {
// mapped types create a variable for their type name, but it's not necessary to reference it,
// so we shouldn't consider it as unused for the purpose of this rule.
context.markVariableAsUsed(node.typeParameter.name.name);
},
TSInterfaceHeritage(node: TSESTree.TSInterfaceHeritage) {
if (node.expression) {
markHeritageAsUsed(node.expression);
'TSEmptyBodyFunctionExpression, TSFunctionType, TSMethodSignature'(
node:
| TSESTree.TSEmptyBodyFunctionExpression
| TSESTree.TSFunctionType
| TSESTree.TSMethodSignature,
): void {
// function type signature params create variables because they can be referenced within the signature,
// but they obviously aren't unused variables for the purposes of this rule.
for (const param of node.params) {
visitPattern(param, name => {
context.markVariableAsUsed(name.name);
});
}
},
TSClassImplements(node: TSESTree.TSClassImplements) {
if (node.expression) {
markHeritageAsUsed(node.expression);
}
},
'TSParameterProperty Identifier'(node: TSESTree.Identifier) {
// just assume parameter properties are used
[[
'TSParameterProperty > AssignmentPattern > Identifier.left',
'TSParameterProperty > Identifier.parameter',
].join(', ')](node: TSESTree.Identifier): void {
// just assume parameter properties are used as property usage tracking is beyond the scope of this rule
context.markVariableAsUsed(node.name);
},
'TSEnumMember Identifier'(node: TSESTree.Identifier) {
':matches(FunctionDeclaration, FunctionExpression, ArrowFunctionExpression) > Identifier[name="this"].params'(
node: TSESTree.Identifier,
): void {
// this parameters should always be considered used as they're pseudo-parameters
context.markVariableAsUsed(node.name);
},
'*[declare=true] Identifier'(node: TSESTree.Identifier) {
TSEnumDeclaration(): void {
// enum members create variables because they can be referenced within the enum,
// but they obviously aren't unused variables for the purposes of this rule.
const scope = context.getScope();
for (const variable of scope.variables) {
context.markVariableAsUsed(variable.name);
}
},

// TODO
'*[declare=true] Identifier'(node: TSESTree.Identifier): void {
context.markVariableAsUsed(node.name);
const scope = context.getScope();
const { variableScope } = scope;
Expand All @@ -75,6 +78,14 @@ export default util.createRule<Options, MessageIds>({
}
}
},
});
};

function visitPattern(
node: TSESTree.Node,
cb: (node: TSESTree.Identifier) => void,
): void {
const visitor = new PatternVisitor({}, node, cb);
visitor.visit(node);
}
},
});
Expand Up @@ -14,6 +14,16 @@ const ruleTester = new RuleTester({

ruleTester.run('no-unnecessary-type-assertion', rule, {
valid: [
`
import { TSESTree } from '@typescript-eslint/experimental-utils';
declare const member: TSESTree.TSEnumMember;
if (
member.id.type === AST_NODE_TYPES.Literal &&
typeof member.id.value === 'string'
) {
const name = member.id as TSESTree.StringLiteral;
}
`,
'const foo = 3 as number;',
'const foo = <number>3;',
'const foo = <3>3;',
Expand Down
64 changes: 41 additions & 23 deletions packages/eslint-plugin/tests/rules/no-unused-vars.test.ts
Expand Up @@ -13,6 +13,9 @@ const ruleTester = new RuleTester({
ruleTester.run('no-unused-vars', rule, {
valid: [
`
//
`,
`
import { ClassDecoratorFactory } from 'decorators';
@ClassDecoratorFactory()
export class Foo {}
Expand Down Expand Up @@ -122,22 +125,22 @@ console.log(a);
`,
`
import { Foo } from 'foo';
function bar<T>() {}
function bar<T>(): T {}
bar<Foo>();
`,
`
import { Foo } from 'foo';
const bar = function <T>() {};
const bar = function <T>(): T {};
bar<Foo>();
`,
`
import { Foo } from 'foo';
const bar = <T>() => {};
const bar = <T>(): T => {};
bar<Foo>();
`,
`
import { Foo } from 'foo';
<Foo>(<T>() => {})();
<Foo>(<T>(): T => {})();
`,
`
import { Nullable } from 'nullable';
Expand Down Expand Up @@ -265,14 +268,14 @@ new A();
`
import { Nullable } from 'nullable';
import { Another } from 'some';
interface A {
export interface A {
do(a: Nullable<Another>);
}
`,
`
import { Nullable } from 'nullable';
import { Another } from 'some';
interface A {
export interface A {
other: Nullable<Another>;
}
`,
Expand All @@ -293,7 +296,6 @@ foo();
`
import { Nullable } from 'nullable';
import { SomeOther } from 'some';
import { Another } from 'some';
class A extends Nullable<SomeOther> {
other: Nullable<Another>;
}
Expand All @@ -314,57 +316,69 @@ new A();
import { Nullable } from 'nullable';
import { SomeOther } from 'some';
import { Another } from 'some';
interface A extends Nullable<SomeOther> {
export interface A extends Nullable<SomeOther> {
other: Nullable<Another>;
}
`,
`
import { Nullable } from 'nullable';
import { SomeOther } from 'some';
import { Another } from 'some';
interface A extends Nullable<SomeOther> {
export interface A extends Nullable<SomeOther> {
do(a: Nullable<Another>);
}
`,
`
import { Foo } from './types';
class Bar<T extends Foo> {}
class Bar<T extends Foo> {
prop: T;
}
new Bar<number>();
`,
`
import { Foo, Bar } from './types';
class Baz<T extends Foo & Bar> {}
class Baz<T extends Foo & Bar> {
prop: T;
}
new Baz<any>();
`,
`
import { Foo } from './types';
class Bar<T = Foo> {}
class Bar<T = Foo> {
prop: T;
}
new Bar<number>();
`,
`
import { Foo } from './types';
class Foo<T = any> {}
class Foo<T = any> {
prop: T;
}
new Foo();
`,
`
import { Foo } from './types';
class Foo<T = {}> {}
class Foo<T = {}> {
prop: T;
}
new Foo();
`,
`
import { Foo } from './types';
class Foo<T extends {} = {}> {}
class Foo<T extends {} = {}> {
prop: T;
}
new Foo();
`,
Expand All @@ -388,7 +402,7 @@ new A<Nullable>();
`
import { Nullable } from 'nullable';
import { SomeOther } from 'other';
function foo<T extends Nullable>() {}
function foo<T extends Nullable>(): T {}
foo<SomeOther>();
`,
`
Expand Down Expand Up @@ -473,7 +487,9 @@ export function authenticated(cb: (user: User | null) => void): void {
// https://github.com/bradzacher/eslint-plugin-typescript/issues/33
`
import { Foo } from './types';
export class Bar<T extends Foo> {}
export class Bar<T extends Foo> {
prop: T;
}
`,
`
import webpack from 'webpack';
Expand All @@ -487,7 +503,9 @@ export function foo(options: ExecaOptions): execa {
`,
`
import { Foo, Bar } from './types';
export class Baz<F = Foo & Bar> {}
export class Baz<F = Foo & Bar> {
prop: F;
}
`,
`
// warning 'B' is defined but never used
Expand All @@ -504,7 +522,7 @@ enum FormFieldIds {
PHONE = 'phone',
EMAIL = 'email',
}
interface IFoo {
export interface IFoo {
fieldName: FormFieldIds;
}
`,
Expand All @@ -513,7 +531,7 @@ enum FormFieldIds {
PHONE = 'phone',
EMAIL = 'email',
}
interface IFoo {
export interface IFoo {
fieldName: FormFieldIds.EMAIL;
}
`,
Expand Down Expand Up @@ -640,7 +658,7 @@ export class Foo {}
{
code: `
import { Foo, Bar } from 'foo';
function baz<Foo>() {}
function baz<Foo>(): Foo {}
baz<Bar>();
`,
errors: [
Expand Down Expand Up @@ -772,7 +790,7 @@ new A();
code: `
import { Nullable } from 'nullable';
import { Another } from 'some';
interface A {
export interface A {
do(a: Nullable);
}
`,
Expand All @@ -793,7 +811,7 @@ interface A {
code: `
import { Nullable } from 'nullable';
import { Another } from 'some';
interface A {
export interface A {
other: Nullable;
}
`,
Expand Down
1 change: 1 addition & 0 deletions packages/eslint-plugin/tsconfig.build.json
Expand Up @@ -12,6 +12,7 @@
"references": [
{ "path": "../experimental-utils/tsconfig.build.json" },
{ "path": "../parser/tsconfig.build.json" },
{ "path": "../scope-manager/tsconfig.build.json" },
{ "path": "../typescript-estree/tsconfig.build.json" }
]
}
1 change: 1 addition & 0 deletions packages/parser/package.json
Expand Up @@ -45,6 +45,7 @@
"dependencies": {
"@types/eslint-visitor-keys": "^1.0.0",
"@typescript-eslint/experimental-utils": "3.3.0",
"@typescript-eslint/scope-manager": "3.3.0",
"@typescript-eslint/typescript-estree": "3.3.0",
"eslint-visitor-keys": "^1.1.0"
},
Expand Down

0 comments on commit 07a426a

Please sign in to comment.