diff --git a/.eslintrc.js b/.eslintrc.js
index 6ce31fc7cbd..f6270a413c5 100644
--- a/.eslintrc.js
+++ b/.eslintrc.js
@@ -216,6 +216,7 @@ module.exports = {
'packages/eslint-plugin-internal/tests/rules/**/*.test.ts',
'packages/eslint-plugin-tslint/tests/rules/**/*.test.ts',
'packages/eslint-plugin/tests/rules/**/*.test.ts',
+ 'packages/eslint-plugin/tests/eslint-rules/**/*.test.ts',
],
rules: {
'@typescript-eslint/internal/plugin-test-formatting': 'error',
diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md
index 5f9822ed12f..7c4c2724b0a 100644
--- a/packages/eslint-plugin/README.md
+++ b/packages/eslint-plugin/README.md
@@ -144,7 +144,6 @@ Pro Tip: For larger codebases you may want to consider splitting our linting int
| [`@typescript-eslint/no-unsafe-call`](./docs/rules/no-unsafe-call.md) | Disallows calling an any type value | :heavy_check_mark: | | :thought_balloon: |
| [`@typescript-eslint/no-unsafe-member-access`](./docs/rules/no-unsafe-member-access.md) | Disallows member access on any typed variables | :heavy_check_mark: | | :thought_balloon: |
| [`@typescript-eslint/no-unsafe-return`](./docs/rules/no-unsafe-return.md) | Disallows returning any from a function | :heavy_check_mark: | | :thought_balloon: |
-| [`@typescript-eslint/no-unused-vars-experimental`](./docs/rules/no-unused-vars-experimental.md) | Disallow unused variables and arguments | | | |
| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements | :heavy_check_mark: | | |
| [`@typescript-eslint/prefer-as-const`](./docs/rules/prefer-as-const.md) | Prefer usage of `as const` over literal type | :heavy_check_mark: | :wrench: | |
| [`@typescript-eslint/prefer-for-of`](./docs/rules/prefer-for-of.md) | Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated | | | |
diff --git a/packages/eslint-plugin/docs/rules/no-shadow.md b/packages/eslint-plugin/docs/rules/no-shadow.md
new file mode 100644
index 00000000000..d4b5294696a
--- /dev/null
+++ b/packages/eslint-plugin/docs/rules/no-shadow.md
@@ -0,0 +1,50 @@
+# Disallow variable declarations from shadowing variables declared in the outer scope (`no-shadow`)
+
+## Rule Details
+
+This rule extends the base [`eslint/no-shadow`](https://eslint.org/docs/rules/no-shadow) rule.
+It adds support for TypeScript's `this` parameters, and adds options for TypeScript features.
+
+## How to use
+
+```jsonc
+{
+ // note you must disable the base rule as it can report incorrect errors
+ "no-shadow": "off",
+ "@typescript-eslint/no-shadow": ["error"]
+}
+```
+
+## Options
+
+See [`eslint/no-shadow` options](https://eslint.org/docs/rules/no-shadow#options).
+This rule adds the following options:
+
+```ts
+interface Options extends BaseNoShadowOptions {
+ ignoreTypeValueShadow?: boolean;
+}
+
+const defaultOptions: Options = {
+ ...baseNoShadowDefaultOptions,
+ ignoreTypeValueShadow: true,
+};
+```
+
+### `ignoreTypeValueShadow`
+
+When set to `true`, the rule will ignore when you name a type and a variable with the same name.
+
+Examples of **correct** code with `{ ignoreTypeValueShadow: true }`:
+
+```ts
+type Foo = number;
+const Foo = 1;
+
+interface Bar {
+ prop: number;
+}
+const Bar = 'test';
+```
+
+Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-shadow.md)
diff --git a/packages/eslint-plugin/docs/rules/no-unused-vars.md b/packages/eslint-plugin/docs/rules/no-unused-vars.md
index 44cfae690f8..5eaf167a379 100644
--- a/packages/eslint-plugin/docs/rules/no-unused-vars.md
+++ b/packages/eslint-plugin/docs/rules/no-unused-vars.md
@@ -1,7 +1,5 @@
# Disallow unused variables (`no-unused-vars`)
-## PLEASE READ THIS ISSUE BEFORE USING THIS RULE [#1856](https://github.com/typescript-eslint/typescript-eslint/issues/1856)
-
## Rule Details
This rule extends the base [`eslint/no-unused-vars`](https://eslint.org/docs/rules/no-unused-vars) rule.
diff --git a/packages/eslint-plugin/docs/rules/no-use-before-define.md b/packages/eslint-plugin/docs/rules/no-use-before-define.md
index 569b8130386..dfdab4d60f7 100644
--- a/packages/eslint-plugin/docs/rules/no-use-before-define.md
+++ b/packages/eslint-plugin/docs/rules/no-use-before-define.md
@@ -23,42 +23,34 @@ See [`eslint/no-use-before-define` options](https://eslint.org/docs/rules/no-use
This rule adds the following options:
```ts
-interface Options extends BaseNoMagicNumbersOptions {
+interface Options extends BaseNoUseBeforeDefineOptions {
enums?: boolean;
typedefs?: boolean;
+ ignoreTypeReferences?: boolean;
}
const defaultOptions: Options = {
- ...baseNoMagicNumbersDefaultOptions,
+ ...baseNoUseBeforeDefineDefaultOptions,
enums: true,
typedefs: true,
+ ignoreTypeReferences: true,
};
```
### `enums`
-The flag which shows whether or not this rule checks enum declarations of upper scopes.
If this is `true`, this rule warns every reference to a enum before the enum declaration.
-Otherwise, ignores those references.
+If this is `false`, this rule will ignore references to enums, when the reference is in a child scope.
-Examples of **incorrect** code for the `{ "enums": true }` option:
+Examples of **incorrect** code for the `{ "enums": false }` option:
```ts
-/*eslint no-use-before-define: ["error", { "enums": true }]*/
+/*eslint no-use-before-define: ["error", { "enums": false }]*/
-function foo() {
- return Foo.FOO;
-}
-
-class Test {
- foo() {
- return Foo.FOO;
- }
-}
+const x = Foo.FOO;
enum Foo {
FOO,
- BAR,
}
```
@@ -78,10 +70,8 @@ enum Foo {
### `typedefs`
-The flag which shows whether or not this rule checks type declarations.
If this is `true`, this rule warns every reference to a type before the type declaration.
-Otherwise, ignores those references.
-Type declarations are hoisted, so it's safe.
+If this is `false`, this rule will ignore references to types.
Examples of **correct** code for the `{ "typedefs": false }` option:
@@ -92,4 +82,25 @@ let myVar: StringOrNumber;
type StringOrNumber = string | number;
```
-Copied from [the original ESLint rule docs](https://github.com/eslint/eslint/blob/a113cd3/docs/rules/no-use-before-define.md)
+### `ignoreTypeReferences`
+
+If this is `true`, this rule ignores all type references, such as in type annotations and assertions.
+If this is `false`, this will will check all type references.
+
+Examples of **correct** code for the `{ "ignoreTypeReferences": true }` option:
+
+```ts
+/*eslint no-use-before-define: ["error", { "ignoreTypeReferences": true }]*/
+
+let var1: StringOrNumber;
+type StringOrNumber = string | number;
+
+let var2: Enum;
+enum Enum {}
+```
+
+### Other Options
+
+See [`eslint/no-use-before-define` options](https://eslint.org/docs/rules/no-use-before-define#options).
+
+Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-use-before-define.md)
diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json
index 08a04f8b760..8bdf0c888d7 100644
--- a/packages/eslint-plugin/package.json
+++ b/packages/eslint-plugin/package.json
@@ -43,6 +43,7 @@
},
"dependencies": {
"@typescript-eslint/experimental-utils": "3.4.0",
+ "@typescript-eslint/scope-manager": "3.4.0",
"debug": "^4.1.1",
"functional-red-black-tree": "^1.0.1",
"regexpp": "^3.0.0",
diff --git a/packages/eslint-plugin/src/configs/all.ts b/packages/eslint-plugin/src/configs/all.ts
index 68923e26e8a..dc763f9d9f5 100644
--- a/packages/eslint-plugin/src/configs/all.ts
+++ b/packages/eslint-plugin/src/configs/all.ts
@@ -90,7 +90,6 @@ export = {
'@typescript-eslint/no-unused-expressions': 'error',
'no-unused-vars': 'off',
'@typescript-eslint/no-unused-vars': 'error',
- '@typescript-eslint/no-unused-vars-experimental': 'error',
'no-use-before-define': 'off',
'@typescript-eslint/no-use-before-define': 'error',
'no-useless-constructor': 'off',
diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts
index 817d1232be2..11ed8582c75 100644
--- a/packages/eslint-plugin/src/rules/no-empty-interface.ts
+++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts
@@ -1,8 +1,5 @@
import * as util from '../util';
-import {
- AST_NODE_TYPES,
- TSESLint,
-} from '@typescript-eslint/experimental-utils';
+import { TSESLint } from '@typescript-eslint/experimental-utils';
type Options = [
{
@@ -81,12 +78,7 @@ export default util.createRule({
let useAutoFix = true;
if (util.isDefinitionFile(filename)) {
const scope = context.getScope();
- if (
- scope.block.parent &&
- scope.block.parent.type ===
- AST_NODE_TYPES.TSModuleDeclaration &&
- scope.block.parent.declare
- ) {
+ if (scope.type === 'tsModule' && scope.block.declare) {
useAutoFix = false;
}
}
diff --git a/packages/eslint-plugin/src/rules/no-shadow.ts b/packages/eslint-plugin/src/rules/no-shadow.ts
new file mode 100644
index 00000000000..aab60bc8163
--- /dev/null
+++ b/packages/eslint-plugin/src/rules/no-shadow.ts
@@ -0,0 +1,284 @@
+import {
+ TSESTree,
+ TSESLint,
+ AST_NODE_TYPES,
+} from '@typescript-eslint/experimental-utils';
+import * as util from '../util';
+
+type MessageIds = 'noShadow';
+type Options = [
+ {
+ allow?: string[];
+ builtinGlobals?: boolean;
+ hoist?: 'all' | 'functions' | 'never';
+ ignoreTypeValueShadow?: boolean;
+ },
+];
+
+export default util.createRule({
+ name: 'no-shadow',
+ meta: {
+ type: 'suggestion',
+ docs: {
+ description:
+ 'Disallow variable declarations from shadowing variables declared in the outer scope',
+ category: 'Variables',
+ recommended: false,
+ extendsBaseRule: true,
+ },
+ schema: [
+ {
+ type: 'object',
+ properties: {
+ builtinGlobals: {
+ type: 'boolean',
+ },
+ hoist: {
+ enum: ['all', 'functions', 'never'],
+ },
+ allow: {
+ type: 'array',
+ items: {
+ type: 'string',
+ },
+ },
+ ignoreTypeValueShadow: {
+ type: 'boolean',
+ },
+ },
+ additionalProperties: false,
+ },
+ ],
+ messages: {
+ noShadow: "'{{name}}' is already declared in the upper scope.",
+ },
+ },
+ defaultOptions: [
+ {
+ allow: [],
+ builtinGlobals: false,
+ hoist: 'functions',
+ ignoreTypeValueShadow: true,
+ },
+ ],
+ create(context, [options]) {
+ /**
+ * Check if variable is a `this` parameter.
+ */
+ function isThisParam(variable: TSESLint.Scope.Variable): boolean {
+ return variable.defs[0].type === 'Parameter' && variable.name === 'this';
+ }
+
+ function isTypeValueShadow(
+ variable: TSESLint.Scope.Variable,
+ shadowed: TSESLint.Scope.Variable,
+ ): boolean {
+ if (options.ignoreTypeValueShadow !== true) {
+ return false;
+ }
+
+ if (
+ !('isValueVariable' in shadowed) ||
+ !('isValueVariable' in variable)
+ ) {
+ // one of them is an eslint global variable
+ return false;
+ }
+
+ return variable.isValueVariable !== shadowed.isValueVariable;
+ }
+
+ /**
+ * Check if variable name is allowed.
+ * @param variable The variable to check.
+ * @returns Whether or not the variable name is allowed.
+ */
+ function isAllowed(variable: TSESLint.Scope.Variable): boolean {
+ return options.allow!.indexOf(variable.name) !== -1;
+ }
+
+ /**
+ * Checks if a variable of the class name in the class scope of ClassDeclaration.
+ *
+ * ClassDeclaration creates two variables of its name into its outer scope and its class scope.
+ * So we should ignore the variable in the class scope.
+ * @param variable The variable to check.
+ * @returns Whether or not the variable of the class name in the class scope of ClassDeclaration.
+ */
+ function isDuplicatedClassNameVariable(
+ variable: TSESLint.Scope.Variable,
+ ): boolean {
+ const block = variable.scope.block;
+
+ return (
+ block.type === AST_NODE_TYPES.ClassDeclaration &&
+ block.id === variable.identifiers[0]
+ );
+ }
+
+ /**
+ * Checks if a variable is inside the initializer of scopeVar.
+ *
+ * To avoid reporting at declarations such as `var a = function a() {};`.
+ * But it should report `var a = function(a) {};` or `var a = function() { function a() {} };`.
+ * @param variable The variable to check.
+ * @param scopeVar The scope variable to look for.
+ * @returns Whether or not the variable is inside initializer of scopeVar.
+ */
+ function isOnInitializer(
+ variable: TSESLint.Scope.Variable,
+ scopeVar: TSESLint.Scope.Variable,
+ ): boolean {
+ const outerScope = scopeVar.scope;
+ const outerDef = scopeVar.defs[0];
+ const outer = outerDef?.parent?.range;
+ const innerScope = variable.scope;
+ const innerDef = variable.defs[0];
+ const inner = innerDef?.name.range;
+
+ return !!(
+ outer &&
+ inner &&
+ outer[0] < inner[0] &&
+ inner[1] < outer[1] &&
+ ((innerDef.type === 'FunctionName' &&
+ innerDef.node.type === AST_NODE_TYPES.FunctionExpression) ||
+ innerDef.node.type === AST_NODE_TYPES.ClassExpression) &&
+ outerScope === innerScope.upper
+ );
+ }
+
+ /**
+ * Get a range of a variable's identifier node.
+ * @param variable The variable to get.
+ * @returns The range of the variable's identifier node.
+ */
+ function getNameRange(
+ variable: TSESLint.Scope.Variable,
+ ): TSESTree.Range | undefined {
+ const def = variable.defs[0];
+ return def?.name.range;
+ }
+
+ /**
+ * Checks if a variable is in TDZ of scopeVar.
+ * @param variable The variable to check.
+ * @param scopeVar The variable of TDZ.
+ * @returns Whether or not the variable is in TDZ of scopeVar.
+ */
+ function isInTdz(
+ variable: TSESLint.Scope.Variable,
+ scopeVar: TSESLint.Scope.Variable,
+ ): boolean {
+ const outerDef = scopeVar.defs[0];
+ const inner = getNameRange(variable);
+ const outer = getNameRange(scopeVar);
+
+ return !!(
+ inner &&
+ outer &&
+ inner[1] < outer[0] &&
+ // Excepts FunctionDeclaration if is {"hoist":"function"}.
+ (options.hoist !== 'functions' ||
+ !outerDef ||
+ outerDef.node.type !== AST_NODE_TYPES.FunctionDeclaration)
+ );
+ }
+
+ /**
+ * Finds the variable by a given name in a given scope and its upper scopes.
+ * @param initScope A scope to start find.
+ * @param name A variable name to find.
+ * @returns A found variable or `null`.
+ */
+ function getVariableByName(
+ initScope: TSESLint.Scope.Scope | null,
+ name: string,
+ ): TSESLint.Scope.Variable | null {
+ let scope = initScope;
+
+ while (scope) {
+ const variable = scope.set.get(name);
+
+ if (variable) {
+ return variable;
+ }
+
+ scope = scope.upper;
+ }
+
+ return null;
+ }
+
+ /**
+ * Checks the current context for shadowed variables.
+ * @param {Scope} scope Fixme
+ */
+ function checkForShadows(scope: TSESLint.Scope.Scope): void {
+ const variables = scope.variables;
+
+ for (const variable of variables) {
+ // ignore "arguments"
+ if (variable.identifiers.length === 0) {
+ continue;
+ }
+
+ // this params are pseudo-params that cannot be shadowed
+ if (isThisParam(variable)) {
+ continue;
+ }
+
+ // ignore variables of a class name in the class scope of ClassDeclaration
+ if (isDuplicatedClassNameVariable(variable)) {
+ continue;
+ }
+
+ // ignore configured allowed names
+ if (isAllowed(variable)) {
+ continue;
+ }
+
+ // Gets shadowed variable.
+ const shadowed = getVariableByName(scope.upper, variable.name);
+ if (!shadowed) {
+ continue;
+ }
+
+ // ignore type value variable shadowing if configured
+ if (isTypeValueShadow(variable, shadowed)) {
+ continue;
+ }
+
+ const isESLintGlobal = 'writeable' in shadowed;
+ if (
+ (shadowed.identifiers.length > 0 ||
+ (options.builtinGlobals && isESLintGlobal)) &&
+ !isOnInitializer(variable, shadowed) &&
+ !(options.hoist !== 'all' && isInTdz(variable, shadowed))
+ ) {
+ context.report({
+ node: variable.identifiers[0],
+ messageId: 'noShadow',
+ data: {
+ name: variable.name,
+ },
+ });
+ }
+ }
+ }
+
+ return {
+ 'Program:exit'(): void {
+ const globalScope = context.getScope();
+ const stack = globalScope.childScopes.slice();
+
+ while (stack.length) {
+ const scope = stack.pop()!;
+
+ stack.push(...scope.childScopes);
+ checkForShadows(scope);
+ }
+ },
+ };
+ },
+});
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts b/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts
index c8586973861..bb71a4a458c 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts
+++ b/packages/eslint-plugin/src/rules/no-unused-vars-experimental.ts
@@ -30,6 +30,8 @@ export default util.createRule({
category: 'Best Practices',
recommended: false,
},
+ deprecated: true,
+ replacedBy: ['no-unused-vars'],
schema: [
{
type: 'object',
diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts
index 2c3595f3ba2..14b93be2a93 100644
--- a/packages/eslint-plugin/src/rules/no-unused-vars.ts
+++ b/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, TSESLint } 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';
@@ -26,45 +24,133 @@ export default util.createRule({
const rules = baseRule.create(context);
/**
- * Mark heritage clause as used
- * @param node The node currently being traversed
+ * Gets a list of TS module definitions for a specified variable.
+ * @param variable eslint-scope variable object.
*/
- 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;
+ function getModuleDeclarations(
+ variable: TSESLint.Scope.Variable,
+ ): TSESTree.TSModuleDeclaration[] {
+ const functionDefinitions: TSESTree.TSModuleDeclaration[] = [];
+
+ variable.defs.forEach(def => {
+ // FunctionDeclarations
+ if (def.type === 'TSModuleName') {
+ functionDefinitions.push(def.node);
+ }
+ });
+
+ return functionDefinitions;
+ }
+
+ /**
+ * Determine if an identifier is referencing an enclosing module name.
+ * @param ref The reference to check.
+ * @param nodes The candidate function nodes.
+ * @returns True if it's a self-reference, false if not.
+ */
+ function isSelfReference(
+ ref: TSESLint.Scope.Reference,
+ nodes: TSESTree.Node[],
+ ): boolean {
+ let scope: TSESLint.Scope.Scope | null = ref.from;
+
+ while (scope) {
+ if (nodes.indexOf(scope.block) >= 0) {
+ return true;
+ }
+
+ scope = scope.upper;
}
+
+ return false;
}
- return Object.assign({}, rules, {
- 'TSTypeReference Identifier'(node: TSESTree.Identifier) {
- context.markVariableAsUsed(node.name);
+ return {
+ ...rules,
+ 'TSConstructorType, TSConstructSignatureDeclaration, TSDeclareFunction, TSEmptyBodyFunctionExpression, TSFunctionType, TSMethodSignature'(
+ node:
+ | TSESTree.TSConstructorType
+ | TSESTree.TSConstructSignatureDeclaration
+ | TSESTree.TSDeclareFunction
+ | 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);
+ });
+ }
},
- TSInterfaceHeritage(node: TSESTree.TSInterfaceHeritage) {
- if (node.expression) {
- markHeritageAsUsed(node.expression);
+ 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);
}
},
- TSClassImplements(node: TSESTree.TSClassImplements) {
- if (node.expression) {
- markHeritageAsUsed(node.expression);
+ 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);
+ },
+ TSModuleDeclaration(): void {
+ const childScope = context.getScope();
+ const scope = util.nullThrows(
+ context.getScope().upper,
+ util.NullThrowsReasons.MissingToken(childScope.type, 'upper scope'),
+ );
+ for (const variable of scope.variables) {
+ // check if the only reference to a module's name is a self-reference in its body
+ const moduleNodes = getModuleDeclarations(variable);
+ const isModuleDefinition = moduleNodes.length > 0;
+
+ if (
+ !isModuleDefinition ||
+ // ignore unreferenced module definitions, as the base rule will report on them
+ variable.references.length === 0
+ ) {
+ continue;
+ }
+
+ const isVariableOnlySelfReferenced = variable.references.every(
+ ref => {
+ return isSelfReference(ref, moduleNodes);
+ },
+ );
+
+ if (isVariableOnlySelfReferenced) {
+ context.report({
+ node: variable.identifiers[0],
+ messageId: 'unusedVar',
+ data: {
+ varName: variable.name,
+ action: 'defined',
+ additional: '',
+ },
+ });
+ }
}
},
- '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) {
+
+ // TODO
+ '*[declare=true] Identifier'(node: TSESTree.Identifier): void {
context.markVariableAsUsed(node.name);
const scope = context.getScope();
const { variableScope } = scope;
@@ -75,6 +161,14 @@ export default util.createRule({
}
}
},
- });
+ };
+
+ function visitPattern(
+ node: TSESTree.Node,
+ cb: (node: TSESTree.Identifier) => void,
+ ): void {
+ const visitor = new PatternVisitor({}, node, cb);
+ visitor.visit(node);
+ }
},
});
diff --git a/packages/eslint-plugin/src/rules/no-use-before-define.ts b/packages/eslint-plugin/src/rules/no-use-before-define.ts
index cb6955e8e3d..08d12645d6a 100644
--- a/packages/eslint-plugin/src/rules/no-use-before-define.ts
+++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts
@@ -16,6 +16,7 @@ function parseOptions(options: string | Config | null): Required {
let enums = true;
let variables = true;
let typedefs = true;
+ let ignoreTypeReferences = true;
if (typeof options === 'string') {
functions = options !== 'nofunc';
@@ -25,54 +26,43 @@ function parseOptions(options: string | Config | null): Required {
enums = options.enums !== false;
variables = options.variables !== false;
typedefs = options.typedefs !== false;
+ ignoreTypeReferences = options.ignoreTypeReferences !== false;
}
- return { functions, classes, enums, variables, typedefs };
+ return {
+ functions,
+ classes,
+ enums,
+ variables,
+ typedefs,
+ ignoreTypeReferences,
+ };
}
/**
- * Checks whether or not a given scope is a top level scope.
- */
-function isTopLevelScope(scope: TSESLint.Scope.Scope): boolean {
- return scope.type === 'module' || scope.type === 'global';
-}
-
-/**
- * Checks whether or not a given variable declaration in an upper scope.
+ * Checks whether or not a given variable is a function declaration.
*/
-function isOuterScope(
- variable: TSESLint.Scope.Variable,
- reference: TSESLint.Scope.Reference,
-): boolean {
- if (variable.scope.variableScope === reference.from.variableScope) {
- // allow the same scope only if it's the top level global/module scope
- if (!isTopLevelScope(variable.scope.variableScope)) {
- return false;
- }
- }
-
- return true;
+function isFunction(variable: TSESLint.Scope.Variable): boolean {
+ return variable.defs[0].type === 'FunctionName';
}
/**
- * Checks whether or not a given variable is a function declaration.
+ * Checks whether or not a given variable is a type declaration.
*/
-function isFunction(variable: TSESLint.Scope.Variable): boolean {
- return variable.defs[0].type === 'FunctionName';
+function isTypedef(variable: TSESLint.Scope.Variable): boolean {
+ return variable.defs[0].type === 'Type';
}
/**
- * Checks whether or not a given variable is a enum declaration in an upper function scope.
+ * Checks whether or not a given variable is a enum declaration.
*/
function isOuterEnum(
variable: TSESLint.Scope.Variable,
reference: TSESLint.Scope.Reference,
): boolean {
- const node = variable.defs[0].node as TSESTree.Node;
-
return (
- node.type === AST_NODE_TYPES.TSEnumDeclaration &&
- isOuterScope(variable, reference)
+ variable.defs[0].type == 'TSEnumName' &&
+ variable.scope.variableScope !== reference.from.variableScope
);
}
@@ -84,7 +74,8 @@ function isOuterClass(
reference: TSESLint.Scope.Reference,
): boolean {
return (
- variable.defs[0].type === 'ClassName' && isOuterScope(variable, reference)
+ variable.defs[0].type === 'ClassName' &&
+ variable.scope.variableScope !== reference.from.variableScope
);
}
@@ -96,7 +87,8 @@ function isOuterVariable(
reference: TSESLint.Scope.Reference,
): boolean {
return (
- variable.defs[0].type === 'Variable' && isOuterScope(variable, reference)
+ variable.defs[0].type === 'Variable' &&
+ variable.scope.variableScope !== reference.from.variableScope
);
}
@@ -165,6 +157,7 @@ interface Config {
enums?: boolean;
variables?: boolean;
typedefs?: boolean;
+ ignoreTypeReferences?: boolean;
}
type Options = ['nofunc' | Config];
type MessageIds = 'noUseBeforeDefine';
@@ -196,6 +189,7 @@ export default util.createRule({
enums: { type: 'boolean' },
variables: { type: 'boolean' },
typedefs: { type: 'boolean' },
+ ignoreTypeReferences: { type: 'boolean' },
},
additionalProperties: false,
},
@@ -210,6 +204,7 @@ export default util.createRule({
enums: true,
variables: true,
typedefs: true,
+ ignoreTypeReferences: true,
},
],
create(context, optionsWithDefault) {
@@ -224,17 +219,23 @@ export default util.createRule({
variable: TSESLint.Scope.Variable,
reference: TSESLint.Scope.Reference,
): boolean {
+ if (reference.isTypeReference && options.ignoreTypeReferences) {
+ return false;
+ }
if (isFunction(variable)) {
- return !!options.functions;
+ return options.functions;
}
if (isOuterClass(variable, reference)) {
- return !!options.classes;
+ return options.classes;
}
if (isOuterVariable(variable, reference)) {
- return !!options.variables;
+ return options.variables;
}
if (isOuterEnum(variable, reference)) {
- return !!options.enums;
+ return options.enums;
+ }
+ if (isTypedef(variable)) {
+ return options.typedefs;
}
return true;
diff --git a/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts b/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts
index e67ec96aed2..6d19021fa56 100644
--- a/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts
+++ b/packages/eslint-plugin/tests/eslint-rules/arrow-parens.test.ts
@@ -1,5 +1,5 @@
import rule from 'eslint/lib/rules/arrow-parens';
-import { RuleTester } from '../RuleTester';
+import { RuleTester, noFormat } from '../RuleTester';
const ruleTester = new RuleTester({
parser: '@typescript-eslint/parser',
@@ -8,7 +8,7 @@ const ruleTester = new RuleTester({
ruleTester.run('arrow-parens', rule, {
valid: [
// https://github.com/typescript-eslint/typescript-eslint/issues/14
- 'const foo = (t) => {};',
+ noFormat`const foo = (t) => {};`,
'const foo = (t) => {};',
'const foo = (t: T) => {};',
'const foo = ((t: T) => {});',
@@ -16,7 +16,7 @@ ruleTester.run('arrow-parens', rule, {
`
const foo = (bar: any): void => {
// Do nothing
-}
+};
`,
{
code: 'const foo = t => {};',
diff --git a/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts
index f9c0817772a..0dc4807edb1 100644
--- a/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts
+++ b/packages/eslint-plugin/tests/eslint-rules/no-dupe-args.test.ts
@@ -15,7 +15,7 @@ ruleTester.run('no-dupe-args', rule, {
// https://github.com/eslint/typescript-eslint-parser/issues/535
`
function foo({ bar }: { bar: string }) {
- console.log(bar);
+ console.log(bar);
}
`,
],
diff --git a/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts
index 57a1c42e03e..2fc971bdef6 100644
--- a/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts
+++ b/packages/eslint-plugin/tests/eslint-rules/no-implicit-globals.test.ts
@@ -14,7 +14,7 @@ ruleTester.run('no-implicit-globals', rule, {
// https://github.com/typescript-eslint/typescript-eslint/issues/23
`
function foo() {
- return "bar";
+ return 'bar';
}
module.exports = foo;
diff --git a/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts
index 45ca71b4a79..237f3ea762c 100644
--- a/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts
+++ b/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts
@@ -14,10 +14,24 @@ const ruleTester = new RuleTester({
ruleTester.run('no-redeclare', rule, {
valid: [
- 'var a = 3; var b = function() { var a = 10; };',
- 'var a = 3; a = 10;',
+ `
+var a = 3;
+var b = function () {
+ var a = 10;
+};
+ `,
+ `
+var a = 3;
+a = 10;
+ `,
{
- code: 'if (true) {\n let b = 2;\n} else { \nlet b = 3;\n}',
+ code: `
+if (true) {
+ let b = 2;
+} else {
+ let b = 3;
+}
+ `,
parserOptions: {
ecmaVersion: 6,
},
@@ -52,19 +66,14 @@ ruleTester.run('no-redeclare', rule, {
env: { browser: true },
},
{
- code: 'var self = 1',
+ code: 'var self = 1;',
options: [{ builtinGlobals: true }],
env: { browser: false },
},
- // https://github.com/eslint/typescript-eslint-parser/issues/443
- `
-const Foo = 1;
-type Foo = 1;
- `,
// https://github.com/eslint/typescript-eslint-parser/issues/535
`
function foo({ bar }: { bar: string }) {
- console.log(bar);
+ console.log(bar);
}
`,
`
@@ -80,13 +89,16 @@ interface ParseAndGenerateServicesResult {
`
function A() {}
interface B {}
-type C = Array
+type C = Array;
class D {}
`,
],
invalid: [
{
- code: 'var a = 3; var a = 10;',
+ code: `
+var a = 3;
+var a = 10;
+ `,
parserOptions: { ecmaVersion: 6 },
errors: [
{
@@ -99,7 +111,14 @@ class D {}
],
},
{
- code: 'switch(foo) { case a: var b = 3;\ncase b: var b = 4}',
+ code: `
+switch (foo) {
+ case a:
+ var b = 3;
+ case b:
+ var b = 4;
+}
+ `,
errors: [
{
messageId: 'redeclared',
@@ -111,7 +130,10 @@ class D {}
],
},
{
- code: 'var a = 3; var a = 10;',
+ code: `
+var a = 3;
+var a = 10;
+ `,
errors: [
{
messageId: 'redeclared',
@@ -123,7 +145,10 @@ class D {}
],
},
{
- code: 'var a = {}; var a = [];',
+ code: `
+var a = {};
+var a = [];
+ `,
errors: [
{
messageId: 'redeclared',
@@ -135,7 +160,10 @@ class D {}
],
},
{
- code: 'var a; function a() {}',
+ code: `
+var a;
+function a() {}
+ `,
errors: [
{
messageId: 'redeclared',
@@ -147,7 +175,10 @@ class D {}
],
},
{
- code: 'function a() {} function a() {}',
+ code: `
+function a() {}
+function a() {}
+ `,
errors: [
{
messageId: 'redeclared',
@@ -159,7 +190,10 @@ class D {}
],
},
{
- code: 'var a = function() { }; var a = function() { }',
+ code: `
+var a = function () {};
+var a = function () {};
+ `,
errors: [
{
messageId: 'redeclared',
@@ -171,7 +205,10 @@ class D {}
],
},
{
- code: 'var a = function() { }; var a = new Date();',
+ code: `
+var a = function () {};
+var a = new Date();
+ `,
errors: [
{
messageId: 'redeclared',
@@ -183,7 +220,11 @@ class D {}
],
},
{
- code: 'var a = 3; var a = 10; var a = 15;',
+ code: `
+var a = 3;
+var a = 10;
+var a = 15;
+ `,
errors: [
{
messageId: 'redeclared',
@@ -202,7 +243,10 @@ class D {}
],
},
{
- code: 'var a; var a;',
+ code: `
+var a;
+var a;
+ `,
parserOptions: { sourceType: 'module' },
errors: [
{
@@ -215,7 +259,10 @@ class D {}
],
},
{
- code: 'export var a; var a;',
+ code: `
+export var a;
+var a;
+ `,
parserOptions: { sourceType: 'module' },
errors: [
{
@@ -255,7 +302,10 @@ class D {}
env: { browser: true },
},
{
- code: 'var a; var {a = 0, b: Object = 0} = {};',
+ code: `
+var a;
+var { a = 0, b: Object = 0 } = {};
+ `,
options: [{ builtinGlobals: true }],
parserOptions: { ecmaVersion: 6 },
errors: [
@@ -276,7 +326,10 @@ class D {}
],
},
{
- code: 'var a; var {a = 0, b: Object = 0} = {};',
+ code: `
+var a;
+var { a = 0, b: Object = 0 } = {};
+ `,
options: [{ builtinGlobals: true }],
parserOptions: { ecmaVersion: 6, sourceType: 'module' },
errors: [
@@ -290,7 +343,10 @@ class D {}
],
},
{
- code: 'var a; var {a = 0, b: Object = 0} = {};',
+ code: `
+var a;
+var { a = 0, b: Object = 0 } = {};
+ `,
options: [{ builtinGlobals: true }],
parserOptions: { ecmaVersion: 6, ecmaFeatures: { globalReturn: true } },
errors: [
@@ -304,7 +360,10 @@ class D {}
],
},
{
- code: 'var a; var {a = 0, b: Object = 0} = {};',
+ code: `
+var a;
+var { a = 0, b: Object = 0 } = {};
+ `,
options: [{ builtinGlobals: false }],
parserOptions: { ecmaVersion: 6 },
errors: [
@@ -332,5 +391,38 @@ class D {}
},
],
},
+
+ {
+ code: `
+type T = 1;
+type T = 2;
+ `,
+ errors: [
+ {
+ messageId: 'redeclared',
+ data: {
+ id: 'T',
+ },
+ line: 3,
+ },
+ ],
+ },
+ {
+ code: `
+type NodeListOf = 1;
+ `,
+ options: [{ builtinGlobals: true }],
+ parserOptions: {
+ lib: ['dom'],
+ },
+ errors: [
+ {
+ messageId: 'redeclaredAsBuiltin',
+ data: {
+ id: 'NodeListOf',
+ },
+ },
+ ],
+ },
],
});
diff --git a/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts
index 21e6369d1b2..7a457a61784 100644
--- a/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts
+++ b/packages/eslint-plugin/tests/eslint-rules/no-restricted-globals.test.ts
@@ -16,28 +16,28 @@ ruleTester.run('no-restricted-globals', rule, {
{
code: `
export default class Test {
- private status: string;
- getStatus() {
- return this.status;
- }
+ private status: string;
+ getStatus() {
+ return this.status;
+ }
}
`,
options: ['status'],
},
{
code: `
-type Handler = (event: string) => any
+type Handler = (event: string) => any;
`,
options: ['event'],
},
{
code: `
- const a = foo?.bar?.name
+ const a = foo?.bar?.name;
`,
},
{
code: `
- const a = foo?.bar?.name ?? "foobar"
+ const a = foo?.bar?.name ?? 'foobar';
`,
},
{
@@ -58,9 +58,8 @@ function onClick() {
console.log(event);
}
-fdescribe("foo", function() {
-});
- `,
+fdescribe('foo', function () {});
+ `,
options: ['event'],
errors: [
{
@@ -73,8 +72,8 @@ fdescribe("foo", function() {
},
{
code: `
-confirm("TEST");
- `,
+confirm('TEST');
+ `,
options: ['confirm'],
errors: [
{
@@ -87,8 +86,8 @@ confirm("TEST");
},
{
code: `
-var a = confirm("TEST")?.a;
- `,
+var a = confirm('TEST')?.a;
+ `,
options: ['confirm'],
errors: [
{
diff --git a/packages/eslint-plugin/tests/eslint-rules/no-shadow.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-shadow.test.ts
deleted file mode 100644
index a1b72733b2b..00000000000
--- a/packages/eslint-plugin/tests/eslint-rules/no-shadow.test.ts
+++ /dev/null
@@ -1,33 +0,0 @@
-import rule from 'eslint/lib/rules/no-shadow';
-import { RuleTester } from '../RuleTester';
-
-const ruleTester = new RuleTester({
- parserOptions: {
- ecmaVersion: 6,
- sourceType: 'module',
- ecmaFeatures: {},
- },
- parser: '@typescript-eslint/parser',
-});
-
-ruleTester.run('no-shadow', rule, {
- valid: [
- // https://github.com/eslint/typescript-eslint-parser/issues/459
- `
-type foo = any;
-function bar(foo: any) {}
- `,
- // https://github.com/typescript-eslint/typescript-eslint/issues/20
- `
-export abstract class Foo {}
-export class FooBar extends Foo {}
- `,
- // https://github.com/typescript-eslint/typescript-eslint/issues/207
- `
-function test(this: Foo) {
- function test2(this: Bar) {}
-}
- `,
- ],
- invalid: [],
-});
diff --git a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
index 38d58b482fd..fea74c8aae3 100644
--- a/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
+++ b/packages/eslint-plugin/tests/eslint-rules/no-undef.test.ts
@@ -31,26 +31,35 @@ export default Beemo;
// https://github.com/eslint/typescript-eslint-parser/issues/471
`
class X {
- field = {}
+ field = {};
}
`,
// https://github.com/eslint/typescript-eslint-parser/issues/466
`
-/*globals document, selector */
-const links = document.querySelectorAll( selector ) as NodeListOf
+/*globals document, selector, NodeListOf, HTMLElement */
+const links = document.querySelectorAll(selector) as NodeListOf;
`,
+ {
+ code: `
+/*globals document, selector */
+const links = document.querySelectorAll(selector) as NodeListOf;
+ `,
+ parserOptions: {
+ lib: ['dom'],
+ },
+ },
// https://github.com/eslint/typescript-eslint-parser/issues/437
`
interface Runnable {
- run (): Result
- toString (): string
+ run(): void;
+ toString(): string;
}
`,
// https://github.com/eslint/typescript-eslint-parser/issues/416
`
export type SomeThing = {
- id: string;
-}
+ id: string;
+};
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/20
`
@@ -59,9 +68,26 @@ export class FooBar extends Foo {}
`,
// https://github.com/typescript-eslint/typescript-eslint/issues/18
`
-function eachr(subject: Map): typeof subject;
-function eachr(subject: Object | Array): typeof subject {
- return subject
+interface IteratorCallback {
+ (this: Subject, value: Value, key: Key, subject: Subject): void | false;
+}
+function eachr(
+ subject: Array,
+ callback: IteratorCallback,
+): typeof subject;
+function eachr(
+ subject: Map,
+ callback: IteratorCallback,
+): typeof subject;
+function eachr