From 43fa09c721f60cfb85f5a5a3831425cfaa4dc771 Mon Sep 17 00:00:00 2001 From: Scott O'Hara Date: Thu, 9 May 2019 02:41:37 +1000 Subject: [PATCH 01/41] feat(eslint-plugin): add no-magic-numbers rule (#373) --- packages/eslint-plugin/README.md | 1 + .../docs/rules/no-magic-numbers.md | 44 +++++ .../src/rules/no-magic-numbers.ts | 152 ++++++++++++++++++ .../tests/rules/no-magic-numbers.test.ts | 134 +++++++++++++++ .../eslint-plugin/typings/eslint-rules.d.ts | 22 +++ 5 files changed, 353 insertions(+) create mode 100644 packages/eslint-plugin/docs/rules/no-magic-numbers.md create mode 100644 packages/eslint-plugin/src/rules/no-magic-numbers.ts create mode 100644 packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 044ff064372..b296291f4ae 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -134,6 +134,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | | | | [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop (`no-for-in-array` from TSLint) | | | :thought_balloon: | | [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallows magic numbers. | :heavy_check_mark: | | | [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | | | | [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | :heavy_check_mark: | | | | [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | :heavy_check_mark: | | | diff --git a/packages/eslint-plugin/docs/rules/no-magic-numbers.md b/packages/eslint-plugin/docs/rules/no-magic-numbers.md new file mode 100644 index 00000000000..2ff5aab519d --- /dev/null +++ b/packages/eslint-plugin/docs/rules/no-magic-numbers.md @@ -0,0 +1,44 @@ +# Disallow Magic Numbers (@typescript-eslint/no-magic-numbers) + +'Magic numbers' are numbers that occur multiple times in code without an explicit meaning. +They should preferably be replaced by named constants. + +## Rule Details + +The `@typescript-eslint/no-magic-numbers` rule extends the `no-magic-numbers` rule from ESLint core, and adds support for handling Typescript specific code that would otherwise trigger the rule. + +See the [ESLint documentation](https://eslint.org/docs/rules/no-magic-numbers) for more details on the `no-magic-numbers` rule. + +## Rule Changes + +```cjson +{ + // note you must disable the base rule as it can report incorrect errors + "no-magic-numbers": "off", + "@typescript-eslint/no-magic-numbers": ["error", { "ignoreNumericLiteralTypes": true }] +} +``` + +In addition to the options supported by the `no-magic-numbers` rule in ESLint core, the rule adds the following options: + +### ignoreNumericLiteralTypes + +A boolean to specify if numbers used in Typescript numeric literal types are considered okay. `false` by default. + +Examples of **incorrect** code for the `{ "ignoreNumericLiteralTypes": false }` option: + +```ts +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": false }]*/ + +type SmallPrimes = 2 | 3 | 5 | 7 | 11; +``` + +Examples of **correct** code for the `{ "ignoreNumericLiteralTypes": true }` option: + +```ts +/*eslint @typescript-eslint/no-magic-numbers: ["error", { "ignoreNumericLiteralTypes": true }]*/ + +type SmallPrimes = 2 | 3 | 5 | 7 | 11; +``` + +Taken with ❤️ [from ESLint core](https://github.com/eslint/eslint/blob/master/docs/rules/no-magic-numbers.md) diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts new file mode 100644 index 00000000000..049aa2b35b8 --- /dev/null +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -0,0 +1,152 @@ +/** + * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js) + * @author Scott O'Hara + */ + +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import baseRule from 'eslint/lib/rules/no-magic-numbers'; +import * as util from '../util'; +import { JSONSchema4 } from 'json-schema'; + +type Options = util.InferOptionsTypeFromRule; +type MessageIds = util.InferMessageIdsTypeFromRule; + +const baseRuleSchema = (baseRule.meta.schema as JSONSchema4[])[0]; + +export default util.createRule({ + name: 'no-magic-numbers', + meta: { + type: 'suggestion', + docs: { + description: 'Disallow magic numbers', + category: 'Best Practices', + recommended: false, + }, + // Extend base schema with additional property to ignore TS numeric literal types + schema: [ + { + ...baseRuleSchema, + properties: { + ...baseRuleSchema.properties, + ignoreNumericLiteralTypes: { + type: 'boolean', + }, + }, + }, + ], + messages: baseRule.meta.messages, + }, + defaultOptions: [ + { + ignore: [], + ignoreArrayIndexes: false, + enforceConst: false, + detectObjects: false, + ignoreNumericLiteralTypes: false, + }, + ], + create(context, [options]) { + const rules = baseRule.create(context); + + /** + * Returns whether the node is number literal + * @param node the node literal being evaluated + * @returns true if the node is a number literal + */ + function isNumber(node: TSESTree.Literal): boolean { + return typeof node.value === 'number'; + } + + /** + * Checks if the node grandparent is a Typescript type alias declaration + * @param node the node to be validated. + * @returns true if the node grandparent is a Typescript type alias declaration + * @private + */ + function isGrandparentTSTypeAliasDeclaration(node: TSESTree.Node): boolean { + return node.parent && node.parent.parent + ? node.parent.parent.type === AST_NODE_TYPES.TSTypeAliasDeclaration + : false; + } + + /** + * Checks if the node grandparent is a Typescript union type and its parent is a type alias declaration + * @param node the node to be validated. + * @returns true if the node grandparent is a Typescript untion type and its parent is a type alias declaration + * @private + */ + function isGrandparentTSUnionType(node: TSESTree.Node): boolean { + if ( + node.parent && + node.parent.parent && + node.parent.parent.type === AST_NODE_TYPES.TSUnionType + ) { + return isGrandparentTSTypeAliasDeclaration(node.parent); + } + + return false; + } + + /** + * Checks if the node parent is a Typescript literal type + * @param node the node to be validated. + * @returns true if the node parent is a Typescript literal type + * @private + */ + function isParentTSLiteralType(node: TSESTree.Node): boolean { + return node.parent + ? node.parent.type === AST_NODE_TYPES.TSLiteralType + : false; + } + + /** + * Checks if the node is a valid TypeScript numeric literal type. + * @param node the node to be validated. + * @returns true if the node is a TypeScript numeric literal type. + * @private + */ + function isTSNumericLiteralType(node: TSESTree.Node): boolean { + // For negative numbers, update the parent node + if ( + node.parent && + node.parent.type === AST_NODE_TYPES.UnaryExpression && + node.parent.operator === '-' + ) { + node = node.parent; + } + + // If the parent node is not a TSLiteralType, early return + if (!isParentTSLiteralType(node)) { + return false; + } + + // If the grandparent is a TSTypeAliasDeclaration, ignore + if (isGrandparentTSTypeAliasDeclaration(node)) { + return true; + } + + // If the grandparent is a TSUnionType and it's parent is a TSTypeAliasDeclaration, ignore + if (isGrandparentTSUnionType(node)) { + return true; + } + + return false; + } + + return { + Literal(node) { + // Check TypeScript specific nodes + if ( + options.ignoreNumericLiteralTypes && + isNumber(node) && + isTSNumericLiteralType(node) + ) { + return; + } + + // Let the base rule deal with the rest + rules.Literal(node); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts new file mode 100644 index 00000000000..2b184ad76b2 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/no-magic-numbers.test.ts @@ -0,0 +1,134 @@ +import rule from '../../src/rules/no-magic-numbers'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('no-magic-numbers', rule, { + valid: [ + { + code: 'const FOO = 10;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = "bar";', + }, + { + code: 'type Foo = true;', + }, + { + code: 'type Foo = 1;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = -1;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = 1 | 2 | 3;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + { + code: 'type Foo = 1 | -1;', + options: [{ ignoreNumericLiteralTypes: true }], + }, + ], + + invalid: [ + { + code: 'type Foo = 1;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 12, + }, + ], + }, + { + code: 'type Foo = -1;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '-1', + }, + line: 1, + column: 12, + }, + ], + }, + { + code: 'type Foo = 1 | 2 | 3;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 12, + }, + { + messageId: 'noMagic', + data: { + raw: '2', + }, + line: 1, + column: 16, + }, + { + messageId: 'noMagic', + data: { + raw: '3', + }, + line: 1, + column: 20, + }, + ], + }, + { + code: 'type Foo = 1 | -1;', + options: [{ ignoreNumericLiteralTypes: false }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 12, + }, + { + messageId: 'noMagic', + data: { + raw: '-1', + }, + line: 1, + column: 16, + }, + ], + }, + { + code: 'interface Foo { bar: 1; }', + options: [{ ignoreNumericLiteralTypes: true }], + errors: [ + { + messageId: 'noMagic', + data: { + raw: '1', + }, + line: 1, + column: 22, + }, + ], + }, + ], +}); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index aac61ca5848..28d05114e26 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -174,6 +174,28 @@ declare module 'eslint/lib/rules/no-implicit-globals' { export = rule; } +declare module 'eslint/lib/rules/no-magic-numbers' { + import { TSESTree } from '@typescript-eslint/typescript-estree'; + import RuleModule from 'ts-eslint'; + + const rule: RuleModule< + 'noMagic', + [ + { + ignore?: string[]; + ignoreArrayIndexes?: boolean; + enforceConst?: boolean; + detectObjects?: boolean; + ignoreNumericLiteralTypes?: boolean; + } + ], + { + Literal(node: TSESTree.Literal): void; + } + >; + export = rule; +} + declare module 'eslint/lib/rules/no-redeclare' { import { TSESTree } from '@typescript-eslint/typescript-estree'; import RuleModule from 'ts-eslint'; From 3219aa7609272674e89766ae42ccf64a1a72ad70 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 8 May 2019 18:41:14 -0700 Subject: [PATCH 02/41] fix(eslint-plugin): [unbound-method] Work around class prototype bug (#499) --- .../eslint-plugin/src/rules/unbound-method.ts | 4 +++ .../tests/rules/unbound-method.test.ts | 32 +++++++++++++++++-- 2 files changed, 34 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index a8a0f41b90d..6b177f1dccf 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -74,6 +74,10 @@ export default util.createRule({ function isDangerousMethod(symbol: ts.Symbol, ignoreStatic: boolean) { const { valueDeclaration } = symbol; + if (!valueDeclaration) { + // working around https://github.com/microsoft/TypeScript/issues/31294 + return false; + } switch (valueDeclaration.kind) { case ts.SyntaxKind.MethodDeclaration: diff --git a/packages/eslint-plugin/tests/rules/unbound-method.test.ts b/packages/eslint-plugin/tests/rules/unbound-method.test.ts index e2757630acb..061b4edf524 100644 --- a/packages/eslint-plugin/tests/rules/unbound-method.test.ts +++ b/packages/eslint-plugin/tests/rules/unbound-method.test.ts @@ -97,7 +97,7 @@ instane.boundStatic && 0; ContainsMethods.boundStatic ? 1 : 0; ContainsMethods.unboundStatic ? 1 : 0; -`, + `, `interface RecordA { readonly type: "A" readonly a: {} @@ -111,7 +111,20 @@ type AnyRecord = RecordA | RecordB function test(obj: AnyRecord) { switch (obj.type) { } -}`, +} + `, + // https://github.com/typescript-eslint/typescript-eslint/issues/496 + ` +class CommunicationError { + constructor() { + const x = CommunicationError.prototype; + } +} + `, + ` +class CommunicationError {} +const x = CommunicationError.prototype; + `, ], invalid: [ { @@ -283,5 +296,20 @@ ContainsMethods.unboundStatic; }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/496 + { + code: ` +class CommunicationError { + foo() {} +} +const x = CommunicationError.prototype.foo; + `, + errors: [ + { + line: 5, + messageId: 'unbound', + }, + ], + }, ], }); From 2c36325c7797f84352b3f60cb3ed85c43c572a51 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 8 May 2019 18:41:52 -0700 Subject: [PATCH 03/41] fix(eslint-plugin): [explicit-function-return-type] Add handling for class properties (#502) --- .../rules/explicit-function-return-type.md | 42 +++++-- .../rules/explicit-function-return-type.ts | 116 +++++++++++------- .../explicit-function-return-type.test.ts | 26 ++++ 3 files changed, 129 insertions(+), 55 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md index 46cd2b86077..2812023ffba 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -61,12 +61,19 @@ class Test { The rule accepts an options object with the following properties: -- `allowExpressions` if true, only functions which are part of a declaration will be checked -- `allowTypedFunctionExpressions` if true, type annotations are also allowed on the variable - of a function expression rather than on the function directly. +```ts +type Options = { + // if true, only functions which are part of a declaration will be checked + allowExpressions?: boolean; + // if true, type annotations are also allowed on the variable of a function expression rather than on the function directly. + allowTypedFunctionExpressions?: boolean; +}; -By default, `allowExpressions: false` and `allowTypedFunctionExpressions: false` are used, -meaning all declarations and expressions _must_ have a return type. +const defaults = { + allowExpressions: false, + allowTypedFunctionExpressions: false, +}; +``` ### allowExpressions @@ -88,6 +95,20 @@ const foo = arr.map(i => i * i); ### allowTypedFunctionExpressions +Examples of **incorrect** code for this rule with `{ allowTypedFunctionExpressions: true }`: + +```ts +let arrowFn = () => 'test'; + +let funcExpr = function() { + return 'test'; +}; + +let objectProp = { + foo: () => 1, +}; +``` + Examples of additional **correct** code for this rule with `{ allowTypedFunctionExpressions: true }`: ```ts @@ -100,6 +121,7 @@ let funcExpr: FuncType = function() { }; let asTyped = (() => '') as () => string; +let caasTyped = <() => string>(() => ''); interface ObjectType { foo(): number; @@ -107,14 +129,12 @@ interface ObjectType { let objectProp: ObjectType = { foo: () => 1, }; - -interface ObjectType { - foo(): number; -} - -let asObjectProp = { +let objectPropAs = { foo: () => 1, } as ObjectType; +let objectPropCast = { + foo: () => 1, +}; ``` ## When Not To Use It diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index 9186ef3cab6..6228f014311 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -5,6 +5,7 @@ type Options = [ { allowExpressions?: boolean; allowTypedFunctionExpressions?: boolean; + allowUntypedSetters?: boolean; } ]; type MessageIds = 'missingReturnType'; @@ -41,6 +42,7 @@ export default util.createRule({ { allowExpressions: false, allowTypedFunctionExpressions: false, + allowUntypedSetters: true, }, ], create(context, [options]) { @@ -48,8 +50,9 @@ export default util.createRule({ * Checks if a node is a constructor. * @param node The node to check */ - function isConstructor(node: TSESTree.Node): boolean { + function isConstructor(node: TSESTree.Node | undefined): boolean { return ( + !!node && node.type === AST_NODE_TYPES.MethodDefinition && node.kind === 'constructor' ); @@ -58,14 +61,17 @@ export default util.createRule({ /** * Checks if a node is a setter. */ - function isSetter(node: TSESTree.Node): boolean { + function isSetter(node: TSESTree.Node | undefined): boolean { return ( - node.type === AST_NODE_TYPES.MethodDefinition && node.kind === 'set' + !!node && + node.type === AST_NODE_TYPES.MethodDefinition && + node.kind === 'set' ); } /** * Checks if a node is a variable declarator with a type annotation. + * `const x: Foo = ...` */ function isVariableDeclaratorWithTypeAnnotation( node: TSESTree.Node, @@ -76,41 +82,62 @@ export default util.createRule({ ); } + /** + * Checks if a node is a class property with a type annotation. + * `public x: Foo = ...` + */ + function isClassPropertyWithTypeAnnotation(node: TSESTree.Node): boolean { + return ( + node.type === AST_NODE_TYPES.ClassProperty && !!node.typeAnnotation + ); + } + + /** + * Checks if a node is a type cast + * `(() => {}) as Foo` + * `(() => {})` + */ + function isTypeCast(node: TSESTree.Node): boolean { + return ( + node.type === AST_NODE_TYPES.TSAsExpression || + node.type === AST_NODE_TYPES.TSTypeAssertion + ); + } + /** * Checks if a node belongs to: - * const x: Foo = { prop: () => {} } + * `const x: Foo = { prop: () => {} }` + * `const x = { prop: () => {} } as Foo` + * `const x = { prop: () => {} }` */ - function isPropertyOfObjectVariableDeclaratorWithTypeAnnotation( - node: TSESTree.Node, + function isPropertyOfObjectWithType( + parent: TSESTree.Node | undefined, ): boolean { - let parent = node.parent; if (!parent || parent.type !== AST_NODE_TYPES.Property) { return false; } - parent = parent.parent; - if (!parent || parent.type !== AST_NODE_TYPES.ObjectExpression) { + parent = parent.parent; // this shouldn't happen, checking just in case + /* istanbul ignore if */ if ( + !parent || + parent.type !== AST_NODE_TYPES.ObjectExpression + ) { return false; } - parent = parent.parent; - return !!parent && isVariableDeclaratorWithTypeAnnotation(parent); - } - function isPropertyOfObjectInAsExpression(node: TSESTree.Node): boolean { - let parent = node.parent; - if (!parent || parent.type !== AST_NODE_TYPES.Property) { + parent = parent.parent; // this shouldn't happen, checking just in case + /* istanbul ignore if */ if (!parent) { return false; } - parent = parent.parent; - if (!parent || parent.type !== AST_NODE_TYPES.ObjectExpression) { - return false; - } - parent = parent.parent; - return !!parent && parent.type === AST_NODE_TYPES.TSAsExpression; + + return ( + isTypeCast(parent) || + isClassPropertyWithTypeAnnotation(parent) || + isVariableDeclaratorWithTypeAnnotation(parent) + ); } /** * Checks if a function declaration/expression has a return type. - * @param node The node representing a function. */ function checkFunctionReturnType( node: @@ -119,22 +146,14 @@ export default util.createRule({ | TSESTree.FunctionExpression, ): void { if ( - options.allowExpressions && - node.type !== AST_NODE_TYPES.FunctionDeclaration && - node.parent && - node.parent.type !== AST_NODE_TYPES.VariableDeclarator && - node.parent.type !== AST_NODE_TYPES.MethodDefinition + node.returnType || + isConstructor(node.parent) || + isSetter(node.parent) ) { return; } - if ( - !node.returnType && - node.parent && - !isConstructor(node.parent) && - !isSetter(node.parent) && - util.isTypeScriptFile(context.getFilename()) - ) { + if (util.isTypeScriptFile(context.getFilename())) { context.report({ node, messageId: 'missingReturnType', @@ -144,20 +163,29 @@ export default util.createRule({ /** * Checks if a function declaration/expression has a return type. - * @param {ASTNode} node The node representing a function. */ function checkFunctionExpressionReturnType( node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, ): void { - if ( - options.allowTypedFunctionExpressions && - node.parent && - (isVariableDeclaratorWithTypeAnnotation(node.parent) || - isPropertyOfObjectVariableDeclaratorWithTypeAnnotation(node) || - node.parent.type === AST_NODE_TYPES.TSAsExpression || - isPropertyOfObjectInAsExpression(node)) - ) { - return; + if (node.parent) { + if (options.allowTypedFunctionExpressions) { + if ( + isTypeCast(node.parent) || + isVariableDeclaratorWithTypeAnnotation(node.parent) || + isClassPropertyWithTypeAnnotation(node.parent) || + isPropertyOfObjectWithType(node.parent) + ) { + return; + } + } + + if ( + options.allowExpressions && + node.parent.type !== AST_NODE_TYPES.VariableDeclarator && + node.parent.type !== AST_NODE_TYPES.MethodDefinition + ) { + return; + } } checkFunctionReturnType(node); diff --git a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index 4c242b484cc..efb98d1dd34 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -125,6 +125,11 @@ var funcExpr: Foo = function() { return 'test'; }; code: `const x = (() => {}) as Foo`, options: [{ allowTypedFunctionExpressions: true }], }, + { + filename: 'test.ts', + code: `const x = (() => {})`, + options: [{ allowTypedFunctionExpressions: true }], + }, { filename: 'test.ts', code: ` @@ -137,8 +142,29 @@ const x = { { filename: 'test.ts', code: ` +const x = { + foo: () => {}, +} + `, + options: [{ allowTypedFunctionExpressions: true }], + }, + { + filename: 'test.ts', + code: ` const x: Foo = { foo: () => {}, +} + `, + options: [{ allowTypedFunctionExpressions: true }], + }, + // https://github.com/typescript-eslint/typescript-eslint/issues/484 + { + filename: 'test.ts', + code: ` +type MethodType = () => void; + +class App { + private method: MethodType = () => {} } `, options: [{ allowTypedFunctionExpressions: true }], From 2d15644242ac45737f195ad0c8803c3ed1a85b0b Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 8 May 2019 18:42:31 -0700 Subject: [PATCH 04/41] fix(eslint-plugin): Support more nodes [no-extra-parens] (#465) --- packages/eslint-plugin-tslint/package.json | 5 +- packages/eslint-plugin/package.json | 9 +- .../src/rules/no-extra-parens.ts | 202 ++++++++++++- packages/eslint-plugin/tests/RuleTester.ts | 57 ++++ .../tests/rules/no-extra-parens.test.ts | 275 +++++++++--------- .../eslint-plugin/typings/eslint-rules.d.ts | 50 +++- packages/eslint-plugin/typings/ts-eslint.d.ts | 2 + packages/parser/package.json | 3 +- packages/typescript-estree/package.json | 9 +- .../src/ts-estree/ts-estree.ts | 2 +- 10 files changed, 452 insertions(+), 162 deletions(-) diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 5e5bab756a9..72eb598c64a 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -19,10 +19,11 @@ }, "license": "MIT", "scripts": { - "test": "jest --coverage", - "prebuild": "npm run clean", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "prebuild": "npm run clean", + "test": "jest --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index c3034469f73..e85f8489268 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -25,13 +25,14 @@ "license": "MIT", "main": "dist/index.js", "scripts": { + "build": "tsc -p tsconfig.build.json", + "clean": "rimraf dist/", "docs": "eslint-docs", "docs:check": "eslint-docs check", - "test": "jest --coverage", - "recommended:update": "ts-node tools/update-recommended.ts", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "prebuild": "npm run clean", - "build": "tsc -p tsconfig.build.json", - "clean": "rimraf dist/", + "recommended:update": "ts-node tools/update-recommended.ts", + "test": "jest --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index 3ad963974c2..d14e1e01a5a 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -1,4 +1,4 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; import baseRule from 'eslint/lib/rules/no-extra-parens'; import * as util from '../util'; @@ -22,10 +22,204 @@ export default util.createRule({ create(context) { const rules = baseRule.create(context); + function binaryExp( + node: TSESTree.BinaryExpression | TSESTree.LogicalExpression, + ) { + const rule = rules.BinaryExpression as (n: typeof node) => void; + + // makes the rule think it should skip the left or right + if (node.left.type === AST_NODE_TYPES.TSAsExpression) { + return rule({ + ...node, + left: { + ...node.left, + type: AST_NODE_TYPES.BinaryExpression as any, + }, + }); + } + if (node.right.type === AST_NODE_TYPES.TSAsExpression) { + return rule({ + ...node, + right: { + ...node.right, + type: AST_NODE_TYPES.BinaryExpression as any, + }, + }); + } + + return rule(node); + } + function callExp(node: TSESTree.CallExpression | TSESTree.NewExpression) { + const rule = rules.CallExpression as (n: typeof node) => void; + + if (node.callee.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + return rule({ + ...node, + callee: { + ...node.callee, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rule(node); + } + function unaryUpdateExpression( + node: TSESTree.UnaryExpression | TSESTree.UpdateExpression, + ) { + const rule = rules.UnaryExpression as (n: typeof node) => void; + + if (node.argument.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + return rule({ + ...node, + argument: { + ...node.argument, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rule(node); + } + return Object.assign({}, rules, { - MemberExpression(node: TSESTree.MemberExpression) { - if (node.object.type !== AST_NODE_TYPES.TSAsExpression) { - return rules.MemberExpression(node); + // ArrayExpression + ArrowFunctionExpression(node) { + if (node.body.type !== AST_NODE_TYPES.TSAsExpression) { + return rules.ArrowFunctionExpression(node); + } + }, + // AssignmentExpression + // AwaitExpression + BinaryExpression: binaryExp, + CallExpression: callExp, + // ClassDeclaration + // ClassExpression + ConditionalExpression(node) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + if (node.test.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ConditionalExpression({ + ...node, + test: { + ...node.test, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + if (node.consequent.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ConditionalExpression({ + ...node, + consequent: { + ...node.consequent, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + if (node.alternate.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be rapped + return rules.ConditionalExpression({ + ...node, + alternate: { + ...node.alternate, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + return rules.ConditionalExpression(node); + }, + // DoWhileStatement + 'ForInStatement, ForOfStatement'( + node: TSESTree.ForInStatement | TSESTree.ForOfStatement, + ) { + if (node.right.type === AST_NODE_TYPES.TSAsExpression) { + // makes the rule skip checking of the right + return rules['ForInStatement, ForOfStatement']({ + ...node, + type: AST_NODE_TYPES.ForOfStatement as any, + right: { + ...node.right, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rules['ForInStatement, ForOfStatement'](node); + }, + ForStatement(node) { + // make the rule skip the piece by removing it entirely + if (node.init && node.init.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ForStatement({ + ...node, + init: null, + }); + } + if (node.test && node.test.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ForStatement({ + ...node, + test: null, + }); + } + if (node.update && node.update.type === AST_NODE_TYPES.TSAsExpression) { + return rules.ForStatement({ + ...node, + update: null, + }); + } + + return rules.ForStatement(node); + }, + // IfStatement + LogicalExpression: binaryExp, + MemberExpression(node) { + if (node.object.type === AST_NODE_TYPES.TSAsExpression) { + // reduces the precedence of the node so the rule thinks it needs to be wrapped + return rules.MemberExpression({ + ...node, + object: { + ...node.object, + type: AST_NODE_TYPES.SequenceExpression as any, + }, + }); + } + + return rules.MemberExpression(node); + }, + NewExpression: callExp, + // ObjectExpression + // ReturnStatement + // SequenceExpression + SpreadElement(node) { + if (node.argument.type !== AST_NODE_TYPES.TSAsExpression) { + return rules.SpreadElement(node); + } + }, + SwitchCase(node) { + if (node.test.type !== AST_NODE_TYPES.TSAsExpression) { + return rules.SwitchCase(node); + } + }, + // SwitchStatement + ThrowStatement(node) { + if ( + node.argument && + node.argument.type !== AST_NODE_TYPES.TSAsExpression + ) { + return rules.ThrowStatement(node); + } + }, + UnaryExpression: unaryUpdateExpression, + UpdateExpression: unaryUpdateExpression, + // VariableDeclarator + // WhileStatement + // WithStatement - i'm not going to even bother implementing this terrible and never used feature + YieldExpression(node) { + if ( + node.argument && + node.argument.type !== AST_NODE_TYPES.TSAsExpression + ) { + return rules.YieldExpression(node); } }, }); diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index c51fa532a82..d708a53cb4a 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -61,11 +61,68 @@ function getFixturesRootDir() { return path.join(process.cwd(), 'tests/fixtures/'); } +/** + * Converts a batch of single line tests into a number of separate test cases. + * This makes it easier to write tests which use the same options. + * + * Why wouldn't you just leave them as one test? + * Because it makes the test error messages harder to decipher. + * This way each line will fail separately, instead of them all failing together. + */ +function batchedSingleLineTests>( + test: ValidTestCase, +): ValidTestCase[]; +/** + * Converts a batch of single line tests into a number of separate test cases. + * This makes it easier to write tests which use the same options. + * + * Why wouldn't you just leave them as one test? + * Because it makes the test error messages harder to decipher. + * This way each line will fail separately, instead of them all failing together. + * + * Make sure you have your line numbers correct for error reporting, as it will match + * the line numbers up with the split tests! + */ +function batchedSingleLineTests< + TMessageIds extends string, + TOptions extends Readonly +>( + test: InvalidTestCase, +): InvalidTestCase[]; +function batchedSingleLineTests< + TMessageIds extends string, + TOptions extends Readonly +>( + options: ValidTestCase | InvalidTestCase, +): (ValidTestCase | InvalidTestCase)[] { + // eslint counts lines from 1 + const lineOffset = options.code[0] === '\n' ? 2 : 1; + return options.code + .trim() + .split('\n') + .map((code, i) => { + const lineNum = i + lineOffset; + const errors = + 'errors' in options + ? options.errors.filter(e => e.line === lineNum) + : []; + return { + ...options, + code, + errors: errors.map(e => ({ + ...e, + line: 1, + })), + }; + }); +} + export { RuleTester, RunTests, TestCaseError, InvalidTestCase, ValidTestCase, + batchedSingleLineTests, getFixturesRootDir, }; diff --git a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts index 154179c192c..64e93de21c3 100644 --- a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts @@ -1,5 +1,5 @@ import rule from '../../src/rules/no-extra-parens'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, batchedSingleLineTests } from '../RuleTester'; const ruleTester = new RuleTester({ parserOptions: { @@ -12,36 +12,27 @@ const ruleTester = new RuleTester({ ruleTester.run('no-extra-parens', rule, { valid: [ - { + ...batchedSingleLineTests({ code: ` - (0).toString(); - (function(){}) ? a() : b(); - (/^a$/).test(x); - for (a of (b, c)); - for (a of b); - for (a in b, c); - for (a in b); +(0).toString(); +(function(){}) ? a() : b(); +(/^a$/).test(x); +for (a of (b, c)); +for (a of b); +for (a in b, c); +for (a in b); + `, + }), + `t.true((me.get as SinonStub).calledWithExactly('/foo', other));`, + ...batchedSingleLineTests({ + code: ` +while ((foo = bar())) {} +if ((foo = bar())) {} +do; while ((foo = bar())) +for (;(a = b);); `, - }, - { - code: `t.true((me.get as SinonStub).calledWithExactly('/foo', other));`, - }, - { - code: `while ((foo = bar())) {}`, - options: ['all', { conditionalAssign: false }], - }, - { - code: `if ((foo = bar())) {}`, - options: ['all', { conditionalAssign: false }], - }, - { - code: `do; while ((foo = bar()))`, - options: ['all', { conditionalAssign: false }], - }, - { - code: `for (;(a = b););`, options: ['all', { conditionalAssign: false }], - }, + }), { code: ` function a(b) { @@ -66,201 +57,213 @@ ruleTester.run('no-extra-parens', rule, { code: `b => b ? (c = d) : (c = e);`, options: ['all', { returnAssign: false }], }, - { - code: `x = a || (b && c);`, - options: ['all', { nestedBinaryExpressions: false }], - }, - { - code: `x = a + (b * c);`, - options: ['all', { nestedBinaryExpressions: false }], - }, - { - code: `x = (a * b) / c;`, + ...batchedSingleLineTests({ + code: ` +x = a || (b && c); +x = a + (b * c); +x = (a * b) / c; + `, options: ['all', { nestedBinaryExpressions: false }], - }, + }), { code: ` - const Component = (
) - const Component = ( -
- ) +const Component = (
) +const Component = ( +
+) `, options: ['all', { ignoreJSX: 'all' }], }, { code: ` - const Component = ( -
-

-

- ) - const Component = ( -
- ) +const Component = ( +
+

+

+) +const Component = ( +
+) `, options: ['all', { ignoreJSX: 'multi-line' }], }, - { + ...batchedSingleLineTests({ code: ` - const Component = (
) - const Component = (

) +const Component = (
) +const Component = (

) `, options: ['all', { ignoreJSX: 'single-line' }], - }, - { + }), + ...batchedSingleLineTests({ code: ` - const b = a => 1 ? 2 : 3; - const d = c => (1 ? 2 : 3); +const b = a => 1 ? 2 : 3; +const d = c => (1 ? 2 : 3); `, options: ['all', { enforceForArrowConditionals: false }], - }, - { + }), + ...batchedSingleLineTests({ code: ` - (0).toString(); - (Object.prototype.toString.call()); - ({}.toString.call()); - (function(){} ? a() : b()); - (/^a$/).test(x); - a = (b * c); - (a * b) + c; - typeof (a); +(0).toString(); +(Object.prototype.toString.call()); +({}.toString.call()); +(function(){} ? a() : b()); +(/^a$/).test(x); +a = (b * c); +(a * b) + c; +typeof (a); `, options: ['functions'], - }, + }), + ...batchedSingleLineTests({ + code: ` +[a as b]; +() => (1 as 1); +x = a as b; +const x = (1 as 1) | 2; +const x = 1 | (2 as 2); +const x = await (foo as Promise); +const res2 = (fn as foo)(); +(x as boolean) ? 1 : 0; +x ? (1 as 1) : 2; +x ? 1 : (2 as 2); +while (foo as boolean) {}; +do {} while (foo as boolean); +for (let i of ([] as Foo)) {} +for (let i in ({} as Foo)) {} +for ((1 as 1);;) {} +for (;(1 as 1);) {} +for (;;(1 as 1)) {} +if (1 as 1) {} +const x = (1 as 1).toString(); +new (1 as 1)(); +const x = { ...(1 as 1), ...{} }; +throw (1 as 1); +throw 1; +const x = !(1 as 1); +const x = (1 as 1)++; +function *x() { yield (1 as 1); yield 1; } +switch (foo) { case 1: case (2 as 2): } + `, + options: [ + 'all', + { + nestedBinaryExpressions: false, + }, + ], + }), ], invalid: [ - { - code: `a = (b * c);`, + ...batchedSingleLineTests({ + code: ` +a = (b * c); +(a * b) + c; +for (a in (b, c)); +for (a in (b)); +for (a of (b)); +typeof (a); + `, errors: [ { messageId: 'unexpected', - line: 1, + line: 2, column: 5, }, - ], - }, - { - code: `(a * b) + c;`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 3, column: 1, }, - ], - }, - { - code: `for (a in (b, c));`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 4, column: 11, }, - ], - }, - { - code: `for (a in (b));`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 5, column: 11, }, - ], - }, - { - code: `for (a of (b));`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 6, column: 11, }, - ], - }, - { - code: `typeof (a);`, - errors: [ { messageId: 'unexpected', - line: 1, + line: 7, column: 8, }, ], - }, - { + }), + ...batchedSingleLineTests({ code: ` - const Component = (
) - const Component = (

) +const Component = (
) +const Component = (

) `, options: ['all', { ignoreJSX: 'multi-line' }], errors: [ { messageId: 'unexpected', line: 2, - column: 27, + column: 19, }, { messageId: 'unexpected', line: 3, - column: 27, + column: 19, }, ], - }, + }), { code: ` - const Component = ( -
-

-

- ) - const Component = ( -
- ) +const Component = ( +
+

+

+) +const Component = ( +
+) `, options: ['all', { ignoreJSX: 'single-line' }], errors: [ { messageId: 'unexpected', line: 2, - column: 27, + column: 19, }, { messageId: 'unexpected', line: 7, - column: 27, + column: 19, }, ], }, - { - code: `((function foo() {}))();`, + ...batchedSingleLineTests({ + code: ` +((function foo() {}))(); +var y = (function () {return 1;}); + `, options: ['functions'], errors: [ { messageId: 'unexpected', - line: 1, + line: 2, column: 2, }, - ], - }, - { - code: `var y = (function () {return 1;});`, - options: ['functions'], - errors: [ { messageId: 'unexpected', - line: 1, + line: 3, column: 9, }, ], - }, + }), ], }); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 28d05114e26..cf3693453f1 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -359,18 +359,48 @@ declare module 'eslint/lib/rules/no-extra-parens' { const rule: RuleModule< 'unexpected', - ( - | 'all' - | 'functions' - | { - conditionalAssign?: boolean; - returnAssign?: boolean; - nestedBinaryExpressions?: boolean; - ignoreJSX?: 'none' | 'all' | 'multi-line' | 'single-line'; - enforceForArrowConditionals?: boolean; - })[], + [ + 'all' | 'functions', + { + conditionalAssign?: boolean; + returnAssign?: boolean; + nestedBinaryExpressions?: boolean; + ignoreJSX?: 'none' | 'all' | 'multi-line' | 'single-line'; + enforceForArrowConditionals?: boolean; + }? + ], { + ArrayExpression(node: TSESTree.ArrayExpression): void; + ArrowFunctionExpression(node: TSESTree.ArrowFunctionExpression): void; + AssignmentExpression(node: TSESTree.AssignmentExpression): void; + AwaitExpression(node: TSESTree.AwaitExpression): void; + BinaryExpression(node: TSESTree.BinaryExpression): void; + CallExpression(node: TSESTree.CallExpression): void; + ClassDeclaration(node: TSESTree.ClassDeclaration): void; + ClassExpression(node: TSESTree.ClassExpression): void; + ConditionalExpression(node: TSESTree.ConditionalExpression): void; + DoWhileStatement(node: TSESTree.DoWhileStatement): void; + 'ForInStatement, ForOfStatement'( + node: TSESTree.ForInStatement | TSESTree.ForOfStatement, + ): void; + ForStatement(node: TSESTree.ForStatement): void; + IfStatement(node: TSESTree.IfStatement): void; + LogicalExpression(node: TSESTree.LogicalExpression): void; MemberExpression(node: TSESTree.MemberExpression): void; + NewExpression(node: TSESTree.NewExpression): void; + ObjectExpression(node: TSESTree.ObjectExpression): void; + ReturnStatement(node: TSESTree.ReturnStatement): void; + SequenceExpression(node: TSESTree.SequenceExpression): void; + SpreadElement(node: TSESTree.SpreadElement): void; + SwitchCase(node: TSESTree.SwitchCase): void; + SwitchStatement(node: TSESTree.SwitchStatement): void; + ThrowStatement(node: TSESTree.ThrowStatement): void; + UnaryExpression(node: TSESTree.UnaryExpression): void; + UpdateExpression(node: TSESTree.UpdateExpression): void; + VariableDeclarator(node: TSESTree.VariableDeclarator): void; + WhileStatement(node: TSESTree.WhileStatement): void; + WithStatement(node: TSESTree.WithStatement): void; + YieldExpression(node: TSESTree.YieldExpression): void; } >; export = rule; diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts index 83c58d548a6..e32069feccf 100644 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ b/packages/eslint-plugin/typings/ts-eslint.d.ts @@ -562,6 +562,8 @@ declare module 'ts-eslint' { TSUnionType?: RuleFunction; TSUnknownKeyword?: RuleFunction; TSVoidKeyword?: RuleFunction; + UnaryExpression?: RuleFunction; + UpdateExpression?: RuleFunction; VariableDeclaration?: RuleFunction; VariableDeclarator?: RuleFunction; WhileStatement?: RuleFunction; diff --git a/packages/parser/package.json b/packages/parser/package.json index 8c0df30f2f5..5beae5aa527 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -26,9 +26,10 @@ "eslint" ], "scripts": { - "prebuild": "npm run clean", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "prebuild": "npm run clean", "test": "jest --coverage", "typecheck": "tsc --noEmit" }, diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index d4b7665e618..3a57c313925 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -27,13 +27,14 @@ "syntax" ], "scripts": { - "prebuild": "npm run clean", + "ast-alignment-tests": "jest spec.ts", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "prebuild": "npm run clean", "test": "jest --coverage", - "unit-tests": "jest \"./tests/lib/.*\"", - "ast-alignment-tests": "jest spec.ts", - "typecheck": "tsc --noEmit" + "typecheck": "tsc --noEmit", + "unit-tests": "jest \"./tests/lib/.*\"" }, "dependencies": { "lodash.unescape": "4.0.1", diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index 9de0fc257d1..e35384b6081 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -964,7 +964,7 @@ export interface ThisExpression extends BaseNode { export interface ThrowStatement extends BaseNode { type: AST_NODE_TYPES.ThrowStatement; - argument: Statement | null; + argument: Statement | TSAsExpression | null; } export interface TryStatement extends BaseNode { From 298d66c5ec5c03dd2c3519cb8e54576ec777948c Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 8 May 2019 18:54:52 -0700 Subject: [PATCH 05/41] fix(eslint-plugin): [no-extra-parens] Fix build error --- packages/eslint-plugin/src/rules/no-extra-parens.ts | 6 ++++-- 1 file changed, 4 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index d14e1e01a5a..78065969afb 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -1,4 +1,5 @@ import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { RuleListener } from 'ts-eslint'; import baseRule from 'eslint/lib/rules/no-extra-parens'; import * as util from '../util'; @@ -84,7 +85,7 @@ export default util.createRule({ return rule(node); } - return Object.assign({}, rules, { + const overrides: RuleListener = { // ArrayExpression ArrowFunctionExpression(node) { if (node.body.type !== AST_NODE_TYPES.TSAsExpression) { @@ -222,6 +223,7 @@ export default util.createRule({ return rules.YieldExpression(node); } }, - }); + }; + return Object.assign({}, rules, overrides); }, }); From 4cd5590208188beb2e91afa96fe5c0627d3fe895 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 9 May 2019 09:32:28 -0700 Subject: [PATCH 06/41] feat(eslint-plugin): Add better non-null handling [no-unnecessary-type-assertion] (#478) --- .eslintrc.json | 1 + .../eslint-plugin/src/rules/member-naming.ts | 8 +- .../src/rules/no-extraneous-class.ts | 4 +- .../rules/no-unnecessary-type-assertion.ts | 233 ++++++++++++------ packages/eslint-plugin/src/util/types.ts | 54 +++- .../no-unnecessary-type-assertion.test.ts | 158 ++++++++++++ 6 files changed, 381 insertions(+), 77 deletions(-) diff --git a/.eslintrc.json b/.eslintrc.json index 818b90dfab5..2039fcec10e 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -8,6 +8,7 @@ "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], "rules": { "comma-dangle": ["error", "always-multiline"], + "curly": ["error", "all"], "no-mixed-operators": "error", "no-console": "off", "no-undef": "off", diff --git a/packages/eslint-plugin/src/rules/member-naming.ts b/packages/eslint-plugin/src/rules/member-naming.ts index 1e6fcdd226e..c0252331f79 100644 --- a/packages/eslint-plugin/src/rules/member-naming.ts +++ b/packages/eslint-plugin/src/rules/member-naming.ts @@ -76,9 +76,13 @@ export default util.createRule({ const convention = conventions[accessibility]; const method = node as TSESTree.MethodDefinition; - if (method.kind === 'constructor') return; + if (method.kind === 'constructor') { + return; + } - if (!convention || convention.test(name)) return; + if (!convention || convention.test(name)) { + return; + } context.report({ node: node.key, diff --git a/packages/eslint-plugin/src/rules/no-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index 4799f211e7d..461b8c6efa8 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -96,7 +96,9 @@ export default util.createRule({ onlyStatic = false; } } - if (!(onlyStatic || onlyConstructor)) break; + if (!(onlyStatic || onlyConstructor)) { + break; + } } if (onlyConstructor) { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index ca86b6c9ca6..1e78c75338d 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,5 +1,15 @@ import { TSESTree } from '@typescript-eslint/typescript-estree'; -import * as tsutils from 'tsutils'; +import { + isCallExpression, + isNewExpression, + isObjectType, + isObjectFlagSet, + isParameterDeclaration, + isPropertyDeclaration, + isStrictCompilerOptionEnabled, + isTypeFlagSet, + isVariableDeclaration, +} from 'tsutils'; import ts from 'typescript'; import * as util from '../util'; @@ -8,7 +18,7 @@ type Options = [ typesToIgnore?: string[]; } ]; -type MessageIds = 'unnecessaryAssertion'; +type MessageIds = 'contextuallyUnnecessary' | 'unnecessaryAssertion'; export default util.createRule({ name: 'no-unnecessary-type-assertion', @@ -24,6 +34,8 @@ export default util.createRule({ messages: { unnecessaryAssertion: 'This assertion is unnecessary since it does not change the type of the expression.', + contextuallyUnnecessary: + 'This assertion is unnecessary since the receiver accepts the original type of the expression.', }, schema: [ { @@ -44,6 +56,8 @@ export default util.createRule({ create(context, [options]) { const sourceCode = context.getSourceCode(); const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + const compilerOptions = parserServices.program.getCompilerOptions(); /** * Sometimes tuple types don't have ObjectFlags.Tuple set, like when they're being matched against an inferred type. @@ -76,91 +90,170 @@ export default util.createRule({ return true; } - function checkNonNullAssertion( - node: TSESTree.Node, + /** + * Returns the contextual type of a given node. + * Contextual type is the type of the target the node is going into. + * i.e. the type of a called function's parameter, or the defined type of a variable declaration + */ + function getContextualType( checker: ts.TypeChecker, - ): void { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get< - ts.NonNullExpression - >(node); - const type = checker.getTypeAtLocation(originalNode.expression); - - if (type === checker.getNonNullableType(type)) { - context.report({ - node, - messageId: 'unnecessaryAssertion', - fix(fixer) { - return fixer.removeRange([ - originalNode.expression.end, - originalNode.end, - ]); - }, - }); + node: ts.Expression, + ): ts.Type | undefined { + const parent = node.parent; + if (!parent) { + return; } - } - function verifyCast( - node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, - checker: ts.TypeChecker, - ): void { - if ( - options && - options.typesToIgnore && - options.typesToIgnore.indexOf( - sourceCode.getText(node.typeAnnotation), - ) !== -1 + if (isCallExpression(parent) || isNewExpression(parent)) { + if (node === parent.expression) { + // is the callee, so has no contextual type + return; + } + } else if ( + isVariableDeclaration(parent) || + isPropertyDeclaration(parent) || + isParameterDeclaration(parent) + ) { + return parent.type + ? checker.getTypeFromTypeNode(parent.type) + : undefined; + } else if ( + ![ts.SyntaxKind.TemplateSpan, ts.SyntaxKind.JsxExpression].includes( + parent.kind, + ) ) { + // parent is not something we know we can get the contextual type of return; } + // TODO - support return statement checking - const originalNode = parserServices.esTreeNodeToTSNodeMap.get< - ts.AssertionExpression - >(node); - const castType = checker.getTypeAtLocation(originalNode); + return checker.getContextualType(node); + } + /** + * Returns true if there's a chance the variable has been used before a value has been assigned to it + */ + function isPossiblyUsedBeforeAssigned(node: ts.Expression): boolean { + const declaration = util.getDeclaration(checker, node); if ( - tsutils.isTypeFlagSet(castType, ts.TypeFlags.Literal) || - (tsutils.isObjectType(castType) && - (tsutils.isObjectFlagSet(castType, ts.ObjectFlags.Tuple) || - couldBeTupleType(castType))) + // non-strict mode doesn't care about used before assigned errors + isStrictCompilerOptionEnabled(compilerOptions, 'strictNullChecks') && + // ignore class properties as they are compile time guarded + // also ignore function arguments as they can't be used before defined + isVariableDeclaration(declaration) && + // is it `const x!: number` + declaration.initializer === undefined && + declaration.exclamationToken === undefined && + declaration.type !== undefined ) { - // It's not always safe to remove a cast to a literal type or tuple - // type, as those types are sometimes widened without the cast. - return; + // check if the defined variable type has changed since assignment + const declarationType = checker.getTypeFromTypeNode(declaration.type); + const type = util.getConstrainedTypeAtLocation(checker, node); + if (declarationType === type) { + // possibly used before assigned, so just skip it + // better to false negative and skip it, than false postiive and fix to compile erroring code + // + // no better way to figure this out right now + // https://github.com/Microsoft/TypeScript/issues/31124 + return true; + } } + return false; + } + + return { + TSNonNullExpression(node) { + const originalNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.NonNullExpression + >(node); + const type = util.getConstrainedTypeAtLocation( + checker, + originalNode.expression, + ); + + if (!util.isNullableType(type)) { + if (isPossiblyUsedBeforeAssigned(originalNode.expression)) { + return; + } - const uncastType = checker.getTypeAtLocation(originalNode.expression); - - if (uncastType === castType) { - context.report({ - node, - messageId: 'unnecessaryAssertion', - fix(fixer) { - return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression - ? fixer.removeRange([ - originalNode.getStart(), - originalNode.expression.getStart(), - ]) - : fixer.removeRange([ + context.report({ + node, + messageId: 'unnecessaryAssertion', + fix(fixer) { + return fixer.removeRange([ + originalNode.expression.end, + originalNode.end, + ]); + }, + }); + } else { + // we know it's a nullable type + // so figure out if the variable is used in a place that accepts nullable types + const contextualType = getContextualType(checker, originalNode); + if (contextualType && util.isNullableType(contextualType)) { + context.report({ + node, + messageId: 'contextuallyUnnecessary', + fix(fixer) { + return fixer.removeRange([ originalNode.expression.end, originalNode.end, ]); - }, - }); - } - } + }, + }); + } + } + }, + 'TSAsExpression, TSTypeAssertion'( + node: TSESTree.TSTypeAssertion | TSESTree.TSAsExpression, + ): void { + if ( + options && + options.typesToIgnore && + options.typesToIgnore.indexOf( + sourceCode.getText(node.typeAnnotation), + ) !== -1 + ) { + return; + } - const checker = parserServices.program.getTypeChecker(); + const originalNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.AssertionExpression + >(node); + const castType = checker.getTypeAtLocation(originalNode); - return { - TSNonNullExpression(node) { - checkNonNullAssertion(node, checker); - }, - TSTypeAssertion(node) { - verifyCast(node, checker); - }, - TSAsExpression(node) { - verifyCast(node, checker); + if ( + isTypeFlagSet(castType, ts.TypeFlags.Literal) || + (isObjectType(castType) && + (isObjectFlagSet(castType, ts.ObjectFlags.Tuple) || + couldBeTupleType(castType))) + ) { + // It's not always safe to remove a cast to a literal type or tuple + // type, as those types are sometimes widened without the cast. + return; + } + + const uncastType = checker.getTypeAtLocation(originalNode.expression); + + if (uncastType === castType) { + context.report({ + node, + messageId: 'unnecessaryAssertion', + fix(fixer) { + return originalNode.kind === ts.SyntaxKind.TypeAssertionExpression + ? fixer.removeRange([ + originalNode.getStart(), + originalNode.expression.getStart(), + ]) + : fixer.removeRange([ + originalNode.expression.end, + originalNode.end, + ]); + }, + }); + } + + // TODO - add contextually unnecessary check for this }, }; }, diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index 4e5d455926a..866ff80a45e 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -1,4 +1,9 @@ -import * as tsutils from 'tsutils'; +import { + isTypeFlagSet, + isTypeReference, + isUnionOrIntersectionType, + unionTypeParts, +} from 'tsutils'; import ts from 'typescript'; /** @@ -10,11 +15,11 @@ export function containsTypeByName( type: ts.Type, allowedNames: Set, ): boolean { - if (tsutils.isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { + if (isTypeFlagSet(type, ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { return true; } - if (tsutils.isTypeReference(type)) { + if (isTypeReference(type)) { type = type.target; } @@ -25,7 +30,7 @@ export function containsTypeByName( return true; } - if (tsutils.isUnionOrIntersectionType(type)) { + if (isUnionOrIntersectionType(type)) { return type.types.some(t => containsTypeByName(t, allowedNames)); } @@ -35,3 +40,44 @@ export function containsTypeByName( bases.some(t => containsTypeByName(t, allowedNames)) ); } + +/** + * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. + */ +export function getConstrainedTypeAtLocation( + checker: ts.TypeChecker, + node: ts.Node, +): ts.Type { + const nodeType = checker.getTypeAtLocation(node); + const constrained = checker.getBaseConstraintOfType(nodeType); + + return constrained || nodeType; +} + +/** + * Checks if the given type is (or accepts) nullable + * @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter) + */ +export function isNullableType(type: ts.Type, isReceiver?: boolean): boolean { + let flags: ts.TypeFlags = 0; + for (const t of unionTypeParts(type)) { + flags |= t.flags; + } + + flags = + isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown) + ? -1 + : flags; + + return (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; +} + +/** + * Gets the declaration for the given variable + */ +export function getDeclaration( + checker: ts.TypeChecker, + node: ts.Expression, +): ts.Declaration { + return checker.getSymbolAtLocation(node)!.declarations![0]; +} diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 6d5e76bbffa..21428ba7256 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -55,6 +55,40 @@ type Foo = number; const foo = (3 + 5);`, options: [{ typesToIgnore: ['Foo'] }], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/453 + // the ol' use-before-assign-is-okay-trust-me assertion + ` +let bar: number +bar! + 1 + `, + ` +let bar: undefined | number +bar! + 1 + `, + ` +let bar: number, baz: number +bar! + 1 + `, + ` +function foo(bar: T) { + return bar! +} + `, + ` +declare function nonNull(s: string); +let s: string | null = null; +nonNull(s!); + `, + ` +const x: number | null = null; +const y: number = x!; + `, + ` +const x: number | null = null; +class Foo { + prop: number = x!; +} + `, ], invalid: [ @@ -116,5 +150,129 @@ const foo = (3 + 5);`, }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/453 + { + code: ` +let bar: number = 1 +bar! + 1 + `, + output: ` +let bar: number = 1 +bar + 1 + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + // definite declaration operator + code: ` +let bar!: number +bar! + 1 + `, + output: ` +let bar!: number +bar + 1 + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + code: ` +let bar: number | undefined +bar = 1; +bar! + 1 + `, + output: ` +let bar: number | undefined +bar = 1; +bar + 1 + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 4, + }, + ], + }, + { + code: ` +function foo(bar: T) { + return bar! +} + `, + output: ` +function foo(bar: T) { + return bar +} + `, + errors: [ + { + messageId: 'unnecessaryAssertion', + line: 3, + }, + ], + }, + { + code: ` +declare function nonNull(s: string | null); +let s: string | null = null; +nonNull(s!); + `, + output: ` +declare function nonNull(s: string | null); +let s: string | null = null; +nonNull(s); + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + line: 4, + }, + ], + }, + { + code: ` +const x: number | null = null; +const y: number | null = x!; + `, + output: ` +const x: number | null = null; +const y: number | null = x; + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + line: 3, + }, + ], + }, + { + code: ` +const x: number | null = null; +class Foo { + prop: number | null = x!; +} + `, + output: ` +const x: number | null = null; +class Foo { + prop: number | null = x; +} + `, + errors: [ + { + messageId: 'contextuallyUnnecessary', + line: 4, + }, + ], + }, ], }); From 4e193cab096e91a80526c6833247fdf6f21f9e62 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 9 May 2019 09:45:32 -0700 Subject: [PATCH 07/41] feat(eslint-plugin): no-inferrable-types: Support more primitives (#442) --- .../docs/rules/no-inferrable-types.md | 99 +++++++++-- .../src/rules/no-inferrable-types.ts | 167 ++++++++++++------ .../tests/rules/no-inferrable-types.test.ts | 143 ++++++++------- .../src/ts-estree/ts-estree.ts | 1 + 4 files changed, 281 insertions(+), 129 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-inferrable-types.md b/packages/eslint-plugin/docs/rules/no-inferrable-types.md index be19fc439d6..9dbabe276c3 100644 --- a/packages/eslint-plugin/docs/rules/no-inferrable-types.md +++ b/packages/eslint-plugin/docs/rules/no-inferrable-types.md @@ -9,23 +9,59 @@ and properties where the type can be easily inferred from its value. ## Options -This rule has an options object: +This rule accepts the following options: -```json -{ - "ignoreProperties": false, - "ignoreParameters": false +```ts +interface Options { + ignoreParameters?: boolean; + ignoreProperties?: boolean; } ``` ### Default -When none of the options are truthy, the following patterns are valid: +The default options are: + +```JSON +{ + "ignoreParameters": true, + "ignoreProperties": true, +} +``` + +With these options, the following patterns are valid: ```ts -const foo = 5; -const bar = true; -const baz = 'str'; +const a = 10n; +const a = -10n; +const a = BigInt(10); +const a = -BigInt(10); +const a = false; +const a = true; +const a = Boolean(null); +const a = !0; +const a = 10; +const a = +10; +const a = -10; +const a = Number('1'); +const a = +Number('1'); +const a = -Number('1'); +const a = Infinity; +const a = +Infinity; +const a = -Infinity; +const a = NaN; +const a = +NaN; +const a = -NaN; +const a = null; +const a = /a/; +const a = RegExp('a'); +const a = new RegExp('a'); +const a = 'str'; +const a = `str`; +const a = String(1); +const a = Symbol('a'); +const a = undefined; +const a = void someValue; class Foo { prop = 5; @@ -39,9 +75,36 @@ function fn(a: number, b: boolean, c: string) {} The following are invalid: ```ts -const foo: number = 5; -const bar: boolean = true; -const baz: string = 'str'; +const a: bigint = 10n; +const a: bigint = -10n; +const a: bigint = BigInt(10); +const a: bigint = -BigInt(10); +const a: boolean = false; +const a: boolean = true; +const a: boolean = Boolean(null); +const a: boolean = !0; +const a: number = 10; +const a: number = +10; +const a: number = -10; +const a: number = Number('1'); +const a: number = +Number('1'); +const a: number = -Number('1'); +const a: number = Infinity; +const a: number = +Infinity; +const a: number = -Infinity; +const a: number = NaN; +const a: number = +NaN; +const a: number = -NaN; +const a: null = null; +const a: RegExp = /a/; +const a: RegExp = RegExp('a'); +const a: RegExp = new RegExp('a'); +const a: string = 'str'; +const a: string = `str`; +const a: string = String(1); +const a: symbol = Symbol('a'); +const a: undefined = undefined; +const a: undefined = void someValue; class Foo { prop: number = 5; @@ -50,23 +113,23 @@ class Foo { function fn(a: number = 5, b: boolean = true) {} ``` -### `ignoreProperties` +### `ignoreParameters` When set to true, the following pattern is considered valid: ```ts -class Foo { - prop: number = 5; +function foo(a: number = 5, b: boolean = true) { + // ... } ``` -### `ignoreParameters` +### `ignoreProperties` When set to true, the following pattern is considered valid: ```ts -function foo(a: number = 5, b: boolean = true) { - // ... +class Foo { + prop: number = 5; } ``` diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index 94b8533108d..9ee66b44564 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -47,50 +47,128 @@ export default util.createRule({ }, ], create(context, [{ ignoreParameters, ignoreProperties }]) { + function isFunctionCall(init: TSESTree.Expression, callName: string) { + return ( + init.type === AST_NODE_TYPES.CallExpression && + init.callee.type === AST_NODE_TYPES.Identifier && + init.callee.name === callName + ); + } + function isLiteral(init: TSESTree.Expression, typeName: string) { + return ( + init.type === AST_NODE_TYPES.Literal && typeof init.value === typeName + ); + } + function isIdentifier(init: TSESTree.Expression, ...names: string[]) { + return ( + init.type === AST_NODE_TYPES.Identifier && names.includes(init.name) + ); + } + function hasUnaryPrefix( + init: TSESTree.Expression, + ...operators: string[] + ): init is TSESTree.UnaryExpression { + return ( + init.type === AST_NODE_TYPES.UnaryExpression && + operators.includes(init.operator) + ); + } + + type Keywords = + | TSESTree.TSBigIntKeyword + | TSESTree.TSBooleanKeyword + | TSESTree.TSNumberKeyword + | TSESTree.TSNullKeyword + | TSESTree.TSStringKeyword + | TSESTree.TSSymbolKeyword + | TSESTree.TSUndefinedKeyword + | TSESTree.TSTypeReference; + const keywordMap = { + [AST_NODE_TYPES.TSBigIntKeyword]: 'bigint', + [AST_NODE_TYPES.TSBooleanKeyword]: 'boolean', + [AST_NODE_TYPES.TSNumberKeyword]: 'number', + [AST_NODE_TYPES.TSNullKeyword]: 'null', + [AST_NODE_TYPES.TSStringKeyword]: 'string', + [AST_NODE_TYPES.TSSymbolKeyword]: 'symbol', + [AST_NODE_TYPES.TSUndefinedKeyword]: 'undefined', + }; + /** * Returns whether a node has an inferrable value or not - * @param node the node to check - * @param init the initializer */ function isInferrable( - node: TSESTree.TSTypeAnnotation, + annotation: TSESTree.TypeNode, init: TSESTree.Expression, - ): boolean { - if ( - node.type !== AST_NODE_TYPES.TSTypeAnnotation || - !node.typeAnnotation - ) { - return false; - } + ): annotation is Keywords { + switch (annotation.type) { + case AST_NODE_TYPES.TSBigIntKeyword: { + // note that bigint cannot have + prefixed to it + const unwrappedInit = hasUnaryPrefix(init, '-') + ? init.argument + : init; + + return ( + isFunctionCall(unwrappedInit, 'BigInt') || + unwrappedInit.type === AST_NODE_TYPES.BigIntLiteral + ); + } + + case AST_NODE_TYPES.TSBooleanKeyword: + return ( + hasUnaryPrefix(init, '!') || + isFunctionCall(init, 'Boolean') || + isLiteral(init, 'boolean') + ); - const annotation = node.typeAnnotation; + case AST_NODE_TYPES.TSNumberKeyword: { + const unwrappedInit = hasUnaryPrefix(init, '+', '-') + ? init.argument + : init; - if (annotation.type === AST_NODE_TYPES.TSStringKeyword) { - if (init.type === AST_NODE_TYPES.Literal) { - return typeof init.value === 'string'; + return ( + isIdentifier(unwrappedInit, 'Infinity', 'NaN') || + isFunctionCall(unwrappedInit, 'Number') || + isLiteral(unwrappedInit, 'number') + ); } - return false; - } - if (annotation.type === AST_NODE_TYPES.TSBooleanKeyword) { - return init.type === AST_NODE_TYPES.Literal; - } + case AST_NODE_TYPES.TSNullKeyword: + return init.type === AST_NODE_TYPES.Literal && init.value === null; + + case AST_NODE_TYPES.TSStringKeyword: + return ( + isFunctionCall(init, 'String') || + isLiteral(init, 'string') || + init.type === AST_NODE_TYPES.TemplateLiteral + ); - if (annotation.type === AST_NODE_TYPES.TSNumberKeyword) { - // Infinity is special - if ( - (init.type === AST_NODE_TYPES.UnaryExpression && - init.operator === '-' && - init.argument.type === AST_NODE_TYPES.Identifier && - init.argument.name === 'Infinity') || - (init.type === AST_NODE_TYPES.Identifier && init.name === 'Infinity') - ) { - return true; + case AST_NODE_TYPES.TSSymbolKeyword: + return isFunctionCall(init, 'Symbol'); + + case AST_NODE_TYPES.TSTypeReference: { + if ( + annotation.typeName.type === AST_NODE_TYPES.Identifier && + annotation.typeName.name === 'RegExp' + ) { + const isRegExpLiteral = + init.type === AST_NODE_TYPES.Literal && + init.value instanceof RegExp; + const isRegExpNewCall = + init.type === AST_NODE_TYPES.NewExpression && + init.callee.type === 'Identifier' && + init.callee.name === 'RegExp'; + const isRegExpCall = isFunctionCall(init, 'RegExp'); + + return isRegExpLiteral || isRegExpCall || isRegExpNewCall; + } + + return false; } - return ( - init.type === AST_NODE_TYPES.Literal && typeof init.value === 'number' - ); + case AST_NODE_TYPES.TSUndefinedKeyword: + return ( + hasUnaryPrefix(init, 'void') || isIdentifier(init, 'undefined') + ); } return false; @@ -98,9 +176,6 @@ export default util.createRule({ /** * Reports an inferrable type declaration, if any - * @param node the node being visited - * @param typeNode the type annotation node - * @param initNode the initializer node */ function reportInferrableType( node: @@ -114,25 +189,15 @@ export default util.createRule({ return; } - if (!isInferrable(typeNode, initNode)) { + if (!isInferrable(typeNode.typeAnnotation, initNode)) { return; } - let type = null; - if (typeNode.typeAnnotation.type === AST_NODE_TYPES.TSBooleanKeyword) { - type = 'boolean'; - } else if ( - typeNode.typeAnnotation.type === AST_NODE_TYPES.TSNumberKeyword - ) { - type = 'number'; - } else if ( - typeNode.typeAnnotation.type === AST_NODE_TYPES.TSStringKeyword - ) { - type = 'string'; - } else { - // shouldn't happen... - return; - } + const type = + typeNode.typeAnnotation.type === AST_NODE_TYPES.TSTypeReference + ? // TODO - if we add more references + 'RegExp' + : keywordMap[typeNode.typeAnnotation.type]; context.report({ node, diff --git a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts index a852f943cb5..007fe8042d9 100644 --- a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts @@ -1,5 +1,84 @@ import rule from '../../src/rules/no-inferrable-types'; -import { RuleTester } from '../RuleTester'; +import { RuleTester, InvalidTestCase } from '../RuleTester'; +import { + InferMessageIdsTypeFromRule, + InferOptionsTypeFromRule, +} from '../../src/util'; + +type MessageIds = InferMessageIdsTypeFromRule; +type Options = InferOptionsTypeFromRule; + +function flatten(arr: T[][]): T[] { + return arr.reduce((acc, a) => acc.concat(a), []); +} +const testCases = [ + { + type: 'bigint', + code: ['10n', '-10n', 'BigInt(10)', '-BigInt(10)'], + }, + { + type: 'boolean', + code: ['false', 'true', 'Boolean(null)', '!0'], + }, + { + type: 'number', + code: [ + '10', + '+10', + '-10', + 'Number("1")', + '+Number("1")', + '-Number("1")', + 'Infinity', + '+Infinity', + '-Infinity', + 'NaN', + '+NaN', + '-NaN', + ], + }, + { + type: 'null', + code: ['null'], + }, + { + type: 'RegExp', + code: ['/a/', 'RegExp("a")', 'new RegExp("a")'], + }, + { + type: 'string', + code: ['"str"', "'str'", '`str`', 'String(1)'], + }, + { + type: 'symbol', + code: ['Symbol("a")'], + }, + { + type: 'undefined', + code: ['undefined', 'void someValue'], + }, +]; +const validTestCases = flatten( + testCases.map(c => c.code.map(code => `const a = ${code}`)), +); +const invalidTestCases: InvalidTestCase[] = flatten( + testCases.map(cas => + cas.code.map(code => ({ + code: `const a: ${cas.type} = ${code}`, + output: `const a = ${code}`, + errors: [ + { + messageId: 'noInferrableType', + data: { + type: cas.type, + }, + line: 1, + column: 7, + }, + ], + })), + ), +); const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -7,9 +86,7 @@ const ruleTester = new RuleTester({ ruleTester.run('no-inferrable-types', rule, { valid: [ - 'const a = 5', - 'const a = true', - "const a = 'foo'", + ...validTestCases, "const fn = (a = 5, b = true, c = 'foo') => {}", "const fn = function(a = 5, b = true, c = 'foo') {}", @@ -45,62 +122,8 @@ ruleTester.run('no-inferrable-types', rule, { ], invalid: [ - { - code: 'const a: number = 5', - output: 'const a = 5', - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'number', - }, - line: 1, - column: 7, - }, - ], - }, - { - code: 'const a: number = Infinity', - output: 'const a = Infinity', - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'number', - }, - line: 1, - column: 7, - }, - ], - }, - { - code: 'const a: boolean = true', - output: 'const a = true', - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'boolean', - }, - line: 1, - column: 7, - }, - ], - }, - { - code: "const a: string = 'foo'", - output: "const a = 'foo'", - errors: [ - { - messageId: 'noInferrableType', - data: { - type: 'string', - }, - line: 1, - column: 7, - }, - ], - }, + ...invalidTestCases, + { code: "const fn = (a: number = 5, b: boolean = true, c: string = 'foo') => {}", diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index e35384b6081..da1523fabc5 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -288,6 +288,7 @@ export type Expression = | JSXOpeningFragment | JSXSpreadChild | LogicalExpression + | NewExpression | RestElement | SequenceExpression | SpreadElement From 6eb97d4eceb8c93a2039d10b0955806f71469692 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 9 May 2019 10:08:21 -0700 Subject: [PATCH 08/41] feat(eslint-plugin): (EXPERIMENTAL) begin indent rewrite (#439) --- .eslintrc.json | 1 + .../indent-new-do-not-use/BinarySearchTree.ts | 60 + .../indent-new-do-not-use/OffsetStorage.ts | 277 + .../rules/indent-new-do-not-use/TokenInfo.ts | 65 + .../src/rules/indent-new-do-not-use/index.ts | 1723 +++ .../eslint-plugin/src/util/applyDefault.ts | 2 +- packages/eslint-plugin/src/util/misc.ts | 9 + packages/eslint-plugin/tests/RuleTester.ts | 7 +- .../indent/indent-invalid-fixture-1.js | 530 + .../fixtures/indent/indent-valid-fixture-1.js | 530 + .../tests/rules/indent/indent-eslint.test.ts | 9646 +++++++++++++++++ .../tests/rules/{ => indent}/indent.test.ts | 6 +- .../eslint-plugin/tests/rules/indent/utils.ts | 103 + .../typings/eslint-ast-util.d.ts | 3 + .../eslint-plugin/typings/eslint-rules.d.ts | 6 +- .../typings/functional-red-black-tree.d.ts | 55 + packages/eslint-plugin/typings/ts-eslint.d.ts | 29 +- .../src/ts-estree/ast-node-types.ts | 4 + .../src/ts-estree/ts-estree.ts | 3 +- 19 files changed, 13038 insertions(+), 21 deletions(-) create mode 100644 packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts create mode 100644 packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts create mode 100644 packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts create mode 100644 packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts create mode 100644 packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js create mode 100644 packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js create mode 100644 packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts rename packages/eslint-plugin/tests/rules/{ => indent}/indent.test.ts (99%) create mode 100644 packages/eslint-plugin/tests/rules/indent/utils.ts create mode 100644 packages/eslint-plugin/typings/eslint-ast-util.d.ts create mode 100644 packages/eslint-plugin/typings/functional-red-black-tree.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index 2039fcec10e..91c11fbba92 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -9,6 +9,7 @@ "rules": { "comma-dangle": ["error", "always-multiline"], "curly": ["error", "all"], + "no-dupe-class-members": "off", "no-mixed-operators": "error", "no-console": "off", "no-undef": "off", diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts new file mode 100644 index 00000000000..002fbd3d8ee --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts @@ -0,0 +1,60 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import createTree = require('functional-red-black-tree'); + +export type TokenOrComment = TSESTree.Token | TSESTree.Comment; +export interface TreeValue { + offset: number; + from: TokenOrComment | null; + force: boolean; +} + +/** + * A mutable balanced binary search tree that stores (key, value) pairs. The keys are numeric, and must be unique. + * This is intended to be a generic wrapper around a balanced binary search tree library, so that the underlying implementation + * can easily be swapped out. + */ +export class BinarySearchTree { + private rbTree = createTree(); + + /** + * Inserts an entry into the tree. + */ + public insert(key: number, value: TreeValue): void { + const iterator = this.rbTree.find(key); + + if (iterator.valid) { + this.rbTree = iterator.update(value); + } else { + this.rbTree = this.rbTree.insert(key, value); + } + } + + /** + * Finds the entry with the largest key less than or equal to the provided key + * @returns The found entry, or null if no such entry exists. + */ + public findLe(key: number): { key: number; value: TreeValue } { + const iterator = this.rbTree.le(key); + + return { key: iterator.key, value: iterator.value }; + } + + /** + * Deletes all of the keys in the interval [start, end) + */ + public deleteRange(start: number, end: number): void { + // Exit without traversing the tree if the range has zero size. + if (start === end) { + return; + } + const iterator = this.rbTree.ge(start); + + while (iterator.valid && iterator.key < end) { + this.rbTree = this.rbTree.remove(iterator.key); + iterator.next(); + } + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts new file mode 100644 index 00000000000..1ab3dd71ebb --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts @@ -0,0 +1,277 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TokenInfo } from './TokenInfo'; +import { BinarySearchTree, TokenOrComment } from './BinarySearchTree'; +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +/** + * A class to store information on desired offsets of tokens from each other + */ +export class OffsetStorage { + private tokenInfo: TokenInfo; + private indentSize: number; + private indentType: string; + private tree: BinarySearchTree; + private lockedFirstTokens: WeakMap; + private desiredIndentCache: WeakMap; + private ignoredTokens: WeakSet; + /** + * @param tokenInfo a TokenInfo instance + * @param indentSize The desired size of each indentation level + * @param indentType The indentation character + */ + constructor(tokenInfo: TokenInfo, indentSize: number, indentType: string) { + this.tokenInfo = tokenInfo; + this.indentSize = indentSize; + this.indentType = indentType; + + this.tree = new BinarySearchTree(); + this.tree.insert(0, { offset: 0, from: null, force: false }); + + this.lockedFirstTokens = new WeakMap(); + this.desiredIndentCache = new WeakMap(); + this.ignoredTokens = new WeakSet(); + } + + private getOffsetDescriptor(token: TokenOrComment) { + return this.tree.findLe(token.range[0]).value; + } + + /** + * Sets the offset column of token B to match the offset column of token A. + * **WARNING**: This matches a *column*, even if baseToken is not the first token on its line. In + * most cases, `setDesiredOffset` should be used instead. + * @param baseToken The first token + * @param offsetToken The second token, whose offset should be matched to the first token + */ + public matchOffsetOf( + baseToken: TokenOrComment, + offsetToken: TokenOrComment, + ): void { + /* + * lockedFirstTokens is a map from a token whose indentation is controlled by the "first" option to + * the token that it depends on. For example, with the `ArrayExpression: first` option, the first + * token of each element in the array after the first will be mapped to the first token of the first + * element. The desired indentation of each of these tokens is computed based on the desired indentation + * of the "first" element, rather than through the normal offset mechanism. + */ + this.lockedFirstTokens.set(offsetToken, baseToken); + } + + /** + * Sets the desired offset of a token. + * + * This uses a line-based offset collapsing behavior to handle tokens on the same line. + * For example, consider the following two cases: + * + * ( + * [ + * bar + * ] + * ) + * + * ([ + * bar + * ]) + * + * Based on the first case, it's clear that the `bar` token needs to have an offset of 1 indent level (4 spaces) from + * the `[` token, and the `[` token has to have an offset of 1 indent level from the `(` token. Since the `(` token is + * the first on its line (with an indent of 0 spaces), the `bar` token needs to be offset by 2 indent levels (8 spaces) + * from the start of its line. + * + * However, in the second case `bar` should only be indented by 4 spaces. This is because the offset of 1 indent level + * between the `(` and the `[` tokens gets "collapsed" because the two tokens are on the same line. As a result, the + * `(` token is mapped to the `[` token with an offset of 0, and the rule correctly decides that `bar` should be indented + * by 1 indent level from the start of the line. + * + * This is useful because rule listeners can usually just call `setDesiredOffset` for all the tokens in the node, + * without needing to check which lines those tokens are on. + * + * Note that since collapsing only occurs when two tokens are on the same line, there are a few cases where non-intuitive + * behavior can occur. For example, consider the following cases: + * + * foo( + * ). + * bar( + * baz + * ) + * + * foo( + * ).bar( + * baz + * ) + * + * Based on the first example, it would seem that `bar` should be offset by 1 indent level from `foo`, and `baz` + * should be offset by 1 indent level from `bar`. However, this is not correct, because it would result in `baz` + * being indented by 2 indent levels in the second case (since `foo`, `bar`, and `baz` are all on separate lines, no + * collapsing would occur). + * + * Instead, the correct way would be to offset `baz` by 1 level from `bar`, offset `bar` by 1 level from the `)`, and + * offset the `)` by 0 levels from `foo`. This ensures that the offset between `bar` and the `)` are correctly collapsed + * in the second case. + * + * @param token The token + * @param fromToken The token that `token` should be offset from + * @param offset The desired indent level + */ + public setDesiredOffset( + token: TokenOrComment, + fromToken: TokenOrComment | null, + offset: number, + ): void { + this.setDesiredOffsets(token.range, fromToken, offset); + } + + /** + * Sets the desired offset of all tokens in a range + * It's common for node listeners in this file to need to apply the same offset to a large, contiguous range of tokens. + * Moreover, the offset of any given token is usually updated multiple times (roughly once for each node that contains + * it). This means that the offset of each token is updated O(AST depth) times. + * It would not be performant to store and update the offsets for each token independently, because the rule would end + * up having a time complexity of O(number of tokens * AST depth), which is quite slow for large files. + * + * Instead, the offset tree is represented as a collection of contiguous offset ranges in a file. For example, the following + * list could represent the state of the offset tree at a given point: + * + * * Tokens starting in the interval [0, 15) are aligned with the beginning of the file + * * Tokens starting in the interval [15, 30) are offset by 1 indent level from the `bar` token + * * Tokens starting in the interval [30, 43) are offset by 1 indent level from the `foo` token + * * Tokens starting in the interval [43, 820) are offset by 2 indent levels from the `bar` token + * * Tokens starting in the interval [820, ∞) are offset by 1 indent level from the `baz` token + * + * The `setDesiredOffsets` methods inserts ranges like the ones above. The third line above would be inserted by using: + * `setDesiredOffsets([30, 43], fooToken, 1);` + * + * @param range A [start, end] pair. All tokens with range[0] <= token.start < range[1] will have the offset applied. + * @param fromToken The token that this is offset from + * @param offset The desired indent level + * @param force `true` if this offset should not use the normal collapsing behavior. This should almost always be false. + */ + public setDesiredOffsets( + range: [number, number], + fromToken: TokenOrComment | null, + offset: number = 0, + force: boolean = false, + ): void { + /* + * Offset ranges are stored as a collection of nodes, where each node maps a numeric key to an offset + * descriptor. The tree for the example above would have the following nodes: + * + * * key: 0, value: { offset: 0, from: null } + * * key: 15, value: { offset: 1, from: barToken } + * * key: 30, value: { offset: 1, from: fooToken } + * * key: 43, value: { offset: 2, from: barToken } + * * key: 820, value: { offset: 1, from: bazToken } + * + * To find the offset descriptor for any given token, one needs to find the node with the largest key + * which is <= token.start. To make this operation fast, the nodes are stored in a balanced binary + * search tree indexed by key. + */ + + const descriptorToInsert = { offset, from: fromToken, force }; + + const descriptorAfterRange = this.tree.findLe(range[1]).value; + + const fromTokenIsInRange = + fromToken && + fromToken.range[0] >= range[0] && + fromToken.range[1] <= range[1]; + // this has to be before the delete + insert below or else you'll get into a cycle + const fromTokenDescriptor = fromTokenIsInRange + ? this.getOffsetDescriptor(fromToken!) + : null; + + // First, remove any existing nodes in the range from the tree. + this.tree.deleteRange(range[0] + 1, range[1]); + + // Insert a new node into the tree for this range + this.tree.insert(range[0], descriptorToInsert); + + /* + * To avoid circular offset dependencies, keep the `fromToken` token mapped to whatever it was mapped to previously, + * even if it's in the current range. + */ + if (fromTokenIsInRange) { + this.tree.insert(fromToken!.range[0], fromTokenDescriptor!); + this.tree.insert(fromToken!.range[1], descriptorToInsert); + } + + /* + * To avoid modifying the offset of tokens after the range, insert another node to keep the offset of the following + * tokens the same as it was before. + */ + this.tree.insert(range[1], descriptorAfterRange); + } + + /** + * Gets the desired indent of a token + * @returns The desired indent of the token + */ + public getDesiredIndent(token: TokenOrComment): string { + if (!this.desiredIndentCache.has(token)) { + if (this.ignoredTokens.has(token)) { + /* + * If the token is ignored, use the actual indent of the token as the desired indent. + * This ensures that no errors are reported for this token. + */ + this.desiredIndentCache.set( + token, + this.tokenInfo.getTokenIndent(token), + ); + } else if (this.lockedFirstTokens.has(token)) { + const firstToken = this.lockedFirstTokens.get(token)!; + + this.desiredIndentCache.set( + token, + + // (indentation for the first element's line) + this.getDesiredIndent( + this.tokenInfo.getFirstTokenOfLine(firstToken), + ) + + // (space between the start of the first element's line and the first element) + this.indentType.repeat( + firstToken.loc.start.column - + this.tokenInfo.getFirstTokenOfLine(firstToken).loc.start.column, + ), + ); + } else { + const offsetInfo = this.getOffsetDescriptor(token); + const offset = + offsetInfo.from && + offsetInfo.from.loc.start.line === token.loc.start.line && + !/^\s*?\n/u.test(token.value) && + !offsetInfo.force + ? 0 + : offsetInfo.offset * this.indentSize; + + this.desiredIndentCache.set( + token, + (offsetInfo.from ? this.getDesiredIndent(offsetInfo.from) : '') + + this.indentType.repeat(offset), + ); + } + } + + return this.desiredIndentCache.get(token)!; + } + + /** + * Ignores a token, preventing it from being reported. + */ + ignoreToken(token: TokenOrComment): void { + if (this.tokenInfo.isFirstTokenOfLine(token)) { + this.ignoredTokens.add(token); + } + } + + /** + * Gets the first token that the given token's indentation is dependent on + * @returns The token that the given token depends on, or `null` if the given token is at the top level + */ + getFirstDependency(token: TSESTree.Token): TSESTree.Token | null; + getFirstDependency(token: TokenOrComment): TokenOrComment | null; + getFirstDependency(token: TokenOrComment): TokenOrComment | null { + return this.getOffsetDescriptor(token).from; + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts new file mode 100644 index 00000000000..29aaecdaa6d --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts @@ -0,0 +1,65 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { SourceCode } from 'ts-eslint'; +import { TokenOrComment } from './BinarySearchTree'; + +/** + * A helper class to get token-based info related to indentation + */ +export class TokenInfo { + private sourceCode: SourceCode; + public firstTokensByLineNumber: Map; + + constructor(sourceCode: SourceCode) { + this.sourceCode = sourceCode; + this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce( + (map, token) => { + if (!map.has(token.loc.start.line)) { + map.set(token.loc.start.line, token); + } + if ( + !map.has(token.loc.end.line) && + sourceCode.text + .slice(token.range[1] - token.loc.end.column, token.range[1]) + .trim() + ) { + map.set(token.loc.end.line, token); + } + return map; + }, + new Map(), + ); + } + + /** + * Gets the first token on a given token's line + * @returns The first token on the given line + */ + public getFirstTokenOfLine( + token: TokenOrComment | TSESTree.Node, + ): TokenOrComment { + return this.firstTokensByLineNumber.get(token.loc.start.line)!; + } + + /** + * Determines whether a token is the first token in its line + * @returns `true` if the token is the first on its line + */ + public isFirstTokenOfLine(token: TokenOrComment): boolean { + return this.getFirstTokenOfLine(token) === token; + } + + /** + * Get the actual indent of a token + * @param token Token to examine. This should be the first token on its line. + * @returns The indentation characters that precede the token + */ + public getTokenIndent(token: TokenOrComment): string { + return this.sourceCode.text.slice( + token.range[0] - token.loc.start.column, + token.range[0], + ); + } +} diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts new file mode 100644 index 00000000000..f9a3ebcfb49 --- /dev/null +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -0,0 +1,1723 @@ +//------------------------------------------------------------------------------ +// Requirements +//------------------------------------------------------------------------------ + +import { + AST_NODE_TYPES, + TSESTree, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; +import { createGlobalLinebreakMatcher } from 'eslint/lib/util/ast-utils'; +import { + isOpeningParenToken, + isClosingParenToken, + isNotOpeningParenToken, + isSemicolonToken, + isClosingBracketToken, + isClosingBraceToken, + isOpeningBraceToken, + isNotClosingParenToken, + isColonToken, + isCommentToken, +} from 'eslint-utils'; +import { RuleListener, RuleFunction } from 'ts-eslint'; +import { TokenOrComment } from './BinarySearchTree'; +import { OffsetStorage } from './OffsetStorage'; +import { TokenInfo } from './TokenInfo'; +import { createRule, ExcludeKeys, RequireKeys } from '../../util'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +const KNOWN_NODES = new Set([ + AST_NODE_TYPES.AssignmentExpression, + AST_NODE_TYPES.AssignmentPattern, + AST_NODE_TYPES.ArrayExpression, + AST_NODE_TYPES.ArrayPattern, + AST_NODE_TYPES.ArrowFunctionExpression, + AST_NODE_TYPES.AwaitExpression, + AST_NODE_TYPES.BlockStatement, + AST_NODE_TYPES.BinaryExpression, + AST_NODE_TYPES.BreakStatement, + AST_NODE_TYPES.CallExpression, + AST_NODE_TYPES.CatchClause, + AST_NODE_TYPES.ClassBody, + AST_NODE_TYPES.ClassDeclaration, + AST_NODE_TYPES.ClassExpression, + AST_NODE_TYPES.ConditionalExpression, + AST_NODE_TYPES.ContinueStatement, + AST_NODE_TYPES.DoWhileStatement, + AST_NODE_TYPES.DebuggerStatement, + AST_NODE_TYPES.EmptyStatement, + AST_NODE_TYPES.ExpressionStatement, + AST_NODE_TYPES.ForStatement, + AST_NODE_TYPES.ForInStatement, + AST_NODE_TYPES.ForOfStatement, + AST_NODE_TYPES.FunctionDeclaration, + AST_NODE_TYPES.FunctionExpression, + AST_NODE_TYPES.Identifier, + AST_NODE_TYPES.IfStatement, + AST_NODE_TYPES.Literal, + AST_NODE_TYPES.LabeledStatement, + AST_NODE_TYPES.LogicalExpression, + AST_NODE_TYPES.MemberExpression, + AST_NODE_TYPES.MetaProperty, + AST_NODE_TYPES.MethodDefinition, + AST_NODE_TYPES.NewExpression, + AST_NODE_TYPES.ObjectExpression, + AST_NODE_TYPES.ObjectPattern, + AST_NODE_TYPES.Program, + AST_NODE_TYPES.Property, + AST_NODE_TYPES.RestElement, + AST_NODE_TYPES.ReturnStatement, + AST_NODE_TYPES.SequenceExpression, + AST_NODE_TYPES.SpreadElement, + AST_NODE_TYPES.Super, + AST_NODE_TYPES.SwitchCase, + AST_NODE_TYPES.SwitchStatement, + AST_NODE_TYPES.TaggedTemplateExpression, + AST_NODE_TYPES.TemplateElement, + AST_NODE_TYPES.TemplateLiteral, + AST_NODE_TYPES.ThisExpression, + AST_NODE_TYPES.ThrowStatement, + AST_NODE_TYPES.TryStatement, + AST_NODE_TYPES.UnaryExpression, + AST_NODE_TYPES.UpdateExpression, + AST_NODE_TYPES.VariableDeclaration, + AST_NODE_TYPES.VariableDeclarator, + AST_NODE_TYPES.WhileStatement, + AST_NODE_TYPES.WithStatement, + AST_NODE_TYPES.YieldExpression, + AST_NODE_TYPES.JSXIdentifier, + AST_NODE_TYPES.JSXNamespacedName, + AST_NODE_TYPES.JSXMemberExpression, + AST_NODE_TYPES.JSXEmptyExpression, + AST_NODE_TYPES.JSXExpressionContainer, + AST_NODE_TYPES.JSXElement, + AST_NODE_TYPES.JSXClosingElement, + AST_NODE_TYPES.JSXOpeningElement, + AST_NODE_TYPES.JSXAttribute, + AST_NODE_TYPES.JSXSpreadAttribute, + AST_NODE_TYPES.JSXText, + AST_NODE_TYPES.ExportDefaultDeclaration, + AST_NODE_TYPES.ExportNamedDeclaration, + AST_NODE_TYPES.ExportAllDeclaration, + AST_NODE_TYPES.ExportSpecifier, + AST_NODE_TYPES.ImportDeclaration, + AST_NODE_TYPES.ImportSpecifier, + AST_NODE_TYPES.ImportDefaultSpecifier, + AST_NODE_TYPES.ImportNamespaceSpecifier, + + // Class properties aren't yet supported by eslint... + AST_NODE_TYPES.ClassProperty, + + // ts keywords + AST_NODE_TYPES.TSAbstractKeyword, + AST_NODE_TYPES.TSAnyKeyword, + AST_NODE_TYPES.TSBooleanKeyword, + AST_NODE_TYPES.TSNeverKeyword, + AST_NODE_TYPES.TSNumberKeyword, + AST_NODE_TYPES.TSStringKeyword, + AST_NODE_TYPES.TSSymbolKeyword, + AST_NODE_TYPES.TSUndefinedKeyword, + AST_NODE_TYPES.TSUnknownKeyword, + AST_NODE_TYPES.TSVoidKeyword, + AST_NODE_TYPES.TSNullKeyword, + + // ts specific nodes we want to support + AST_NODE_TYPES.TSAbstractClassProperty, + AST_NODE_TYPES.TSAbstractMethodDefinition, + AST_NODE_TYPES.TSArrayType, + AST_NODE_TYPES.TSAsExpression, + AST_NODE_TYPES.TSCallSignatureDeclaration, + AST_NODE_TYPES.TSConditionalType, + AST_NODE_TYPES.TSConstructorType, + AST_NODE_TYPES.TSConstructSignatureDeclaration, + AST_NODE_TYPES.TSDeclareFunction, + AST_NODE_TYPES.TSEmptyBodyFunctionExpression, + AST_NODE_TYPES.TSEnumDeclaration, + AST_NODE_TYPES.TSEnumMember, + AST_NODE_TYPES.TSExportAssignment, + AST_NODE_TYPES.TSExternalModuleReference, + AST_NODE_TYPES.TSFunctionType, + AST_NODE_TYPES.TSImportType, + AST_NODE_TYPES.TSIndexedAccessType, + AST_NODE_TYPES.TSIndexSignature, + AST_NODE_TYPES.TSInferType, + AST_NODE_TYPES.TSInterfaceBody, + AST_NODE_TYPES.TSInterfaceDeclaration, + AST_NODE_TYPES.TSInterfaceHeritage, + AST_NODE_TYPES.TSIntersectionType, + AST_NODE_TYPES.TSImportEqualsDeclaration, + AST_NODE_TYPES.TSLiteralType, + AST_NODE_TYPES.TSMappedType, + AST_NODE_TYPES.TSMethodSignature, + 'TSMinusToken', + AST_NODE_TYPES.TSModuleBlock, + AST_NODE_TYPES.TSModuleDeclaration, + AST_NODE_TYPES.TSNonNullExpression, + AST_NODE_TYPES.TSParameterProperty, + AST_NODE_TYPES.TSParenthesizedType, + 'TSPlusToken', + AST_NODE_TYPES.TSPropertySignature, + AST_NODE_TYPES.TSQualifiedName, + AST_NODE_TYPES.TSQuestionToken, + AST_NODE_TYPES.TSRestType, + AST_NODE_TYPES.TSThisType, + AST_NODE_TYPES.TSTupleType, + AST_NODE_TYPES.TSTypeAnnotation, + AST_NODE_TYPES.TSTypeLiteral, + AST_NODE_TYPES.TSTypeOperator, + AST_NODE_TYPES.TSTypeParameter, + AST_NODE_TYPES.TSTypeParameterDeclaration, + AST_NODE_TYPES.TSTypeParameterInstantiation, + AST_NODE_TYPES.TSTypeReference, + AST_NODE_TYPES.TSUnionType, +]); +const STATEMENT_LIST_PARENTS = new Set([ + AST_NODE_TYPES.Program, + AST_NODE_TYPES.BlockStatement, + AST_NODE_TYPES.SwitchCase, +]); +const DEFAULT_VARIABLE_INDENT = 1; +const DEFAULT_PARAMETER_INDENT = 1; +const DEFAULT_FUNCTION_BODY_INDENT = 1; + +/* + * General rule strategy: + * 1. An OffsetStorage instance stores a map of desired offsets, where each token has a specified offset from another + * specified token or to the first column. + * 2. As the AST is traversed, modify the desired offsets of tokens accordingly. For example, when entering a + * BlockStatement, offset all of the tokens in the BlockStatement by 1 indent level from the opening curly + * brace of the BlockStatement. + * 3. After traversing the AST, calculate the expected indentation levels of every token according to the + * OffsetStorage container. + * 4. For each line, compare the expected indentation of the first token to the actual indentation in the file, + * and report the token if the two values are not equal. + */ + +const ELEMENT_LIST_SCHEMA = { + oneOf: [ + { + type: 'integer', + minimum: 0, + }, + { + enum: ['first', 'off'], + }, + ], +}; + +interface VariableDeclaratorObj { + var?: ElementList; + let?: ElementList; + const?: ElementList; +} +type ElementList = number | 'first' | 'off'; +interface IndentConfig { + SwitchCase?: number; + VariableDeclarator?: ElementList | VariableDeclaratorObj; + outerIIFEBody?: number; + MemberExpression?: number | 'off'; + FunctionDeclaration?: { + parameters?: ElementList; + body?: number; + }; + FunctionExpression?: { + parameters?: ElementList; + body?: number; + }; + CallExpression?: { + arguments?: ElementList; + }; + ArrayExpression?: ElementList; + ObjectExpression?: ElementList; + ImportDeclaration?: ElementList; + flatTernaryExpressions?: boolean; + ignoredNodes?: string[]; + ignoreComments?: boolean; +} +type Options = [('tab' | number)?, IndentConfig?]; +type MessageIds = 'wrongIndentation'; + +type AppliedOptions = ExcludeKeys< + RequireKeys, + 'VariableDeclarator' +> & { + VariableDeclarator: 'off' | VariableDeclaratorObj; +}; + +export default createRule({ + name: 'indent', + meta: { + type: 'layout', + docs: { + description: 'Enforce consistent indentation.', + category: 'Stylistic Issues', + recommended: false, + }, + fixable: 'whitespace', + schema: [ + { + oneOf: [ + { + enum: ['tab'], + }, + { + type: 'integer', + minimum: 0, + }, + ], + }, + { + type: 'object', + properties: { + SwitchCase: { + type: 'integer', + minimum: 0, + default: 0, + }, + VariableDeclarator: { + oneOf: [ + ELEMENT_LIST_SCHEMA, + { + type: 'object', + properties: { + var: ELEMENT_LIST_SCHEMA, + let: ELEMENT_LIST_SCHEMA, + const: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ], + }, + outerIIFEBody: { + type: 'integer', + minimum: 0, + }, + MemberExpression: { + oneOf: [ + { + type: 'integer', + minimum: 0, + }, + { + enum: ['off'], + }, + ], + }, + FunctionDeclaration: { + type: 'object', + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + FunctionExpression: { + type: 'object', + properties: { + parameters: ELEMENT_LIST_SCHEMA, + body: { + type: 'integer', + minimum: 0, + }, + }, + additionalProperties: false, + }, + CallExpression: { + type: 'object', + properties: { + arguments: ELEMENT_LIST_SCHEMA, + }, + additionalProperties: false, + }, + ArrayExpression: ELEMENT_LIST_SCHEMA, + ObjectExpression: ELEMENT_LIST_SCHEMA, + ImportDeclaration: ELEMENT_LIST_SCHEMA, + flatTernaryExpressions: { + type: 'boolean', + default: false, + }, + ignoredNodes: { + type: 'array', + items: { + type: 'string', + not: { + pattern: ':exit$', + }, + }, + }, + ignoreComments: { + type: 'boolean', + default: false, + }, + }, + additionalProperties: false, + }, + ], + messages: { + wrongIndentation: + 'Expected indentation of {{expected}} but found {{actual}}.', + }, + }, + defaultOptions: [ + // typescript docs and playground use 4 space indent + 4, + { + // typescript docs indent the case from the switch + // https://www.typescriptlang.org/docs/handbook/release-notes/typescript-1-8.html#example-4 + SwitchCase: 1, + VariableDeclarator: { + var: DEFAULT_VARIABLE_INDENT, + let: DEFAULT_VARIABLE_INDENT, + const: DEFAULT_VARIABLE_INDENT, + }, + outerIIFEBody: 1, + FunctionDeclaration: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + FunctionExpression: { + parameters: DEFAULT_PARAMETER_INDENT, + body: DEFAULT_FUNCTION_BODY_INDENT, + }, + CallExpression: { + arguments: DEFAULT_PARAMETER_INDENT, + }, + MemberExpression: 1, + ArrayExpression: 1, + ObjectExpression: 1, + ImportDeclaration: 1, + flatTernaryExpressions: false, + ignoredNodes: [], + ignoreComments: false, + }, + ], + create(context, [userIndent, userOptions]) { + const indentType = userIndent === 'tab' ? 'tab' : 'space'; + const indentSize = userIndent === 'tab' ? 1 : userIndent!; + + const options = userOptions as AppliedOptions; + if ( + typeof userOptions!.VariableDeclarator === 'number' || + userOptions!.VariableDeclarator === 'first' + ) { + // typescript doesn't narrow the type for some reason + options.VariableDeclarator = { + var: userOptions!.VariableDeclarator as number | 'first', + let: userOptions!.VariableDeclarator as number | 'first', + const: userOptions!.VariableDeclarator as number | 'first', + }; + } + + const sourceCode = context.getSourceCode(); + const tokenInfo = new TokenInfo(sourceCode); + const offsets = new OffsetStorage( + tokenInfo, + indentSize, + indentType === 'space' ? ' ' : '\t', + ); + const parameterParens = new WeakSet(); + + /** + * Creates an error message for a line, given the expected/actual indentation. + * @param expectedAmount The expected amount of indentation characters for this line + * @param actualSpaces The actual number of indentation spaces that were found on this line + * @param actualTabs The actual number of indentation tabs that were found on this line + * @returns An error message for this line + */ + function createErrorMessageData( + expectedAmount: number, + actualSpaces: number, + actualTabs: number, + ) { + const expectedStatement = `${expectedAmount} ${indentType}${ + expectedAmount === 1 ? '' : 's' + }`; // e.g. "2 tabs" + const foundSpacesWord = `space${actualSpaces === 1 ? '' : 's'}`; // e.g. "space" + const foundTabsWord = `tab${actualTabs === 1 ? '' : 's'}`; // e.g. "tabs" + let foundStatement; + + if (actualSpaces > 0) { + /* + * Abbreviate the message if the expected indentation is also spaces. + * e.g. 'Expected 4 spaces but found 2' rather than 'Expected 4 spaces but found 2 spaces' + */ + foundStatement = + indentType === 'space' + ? actualSpaces + : `${actualSpaces} ${foundSpacesWord}`; + } else if (actualTabs > 0) { + foundStatement = + indentType === 'tab' ? actualTabs : `${actualTabs} ${foundTabsWord}`; + } else { + foundStatement = '0'; + } + return { + expected: expectedStatement, + actual: foundStatement, + }; + } + + /** + * Reports a given indent violation + * @param token Token violating the indent rule + * @param neededIndent Expected indentation string + */ + function report(token: TokenOrComment, neededIndent: string): void { + const actualIndent = Array.from(tokenInfo.getTokenIndent(token)); + const numSpaces = actualIndent.filter(char => char === ' ').length; + const numTabs = actualIndent.filter(char => char === '\t').length; + + context.report({ + node: token, + messageId: 'wrongIndentation', + data: createErrorMessageData(neededIndent.length, numSpaces, numTabs), + loc: { + start: { line: token.loc.start.line, column: 0 }, + end: { line: token.loc.start.line, column: token.loc.start.column }, + }, + fix(fixer) { + return fixer.replaceTextRange( + [token.range[0] - token.loc.start.column, token.range[0]], + neededIndent, + ); + }, + }); + } + + /** + * Checks if a token's indentation is correct + * @param token Token to examine + * @param desiredIndent Desired indentation of the string + * @returns `true` if the token's indentation is correct + */ + function validateTokenIndent( + token: TokenOrComment, + desiredIndent: string, + ): boolean { + const indentation = tokenInfo.getTokenIndent(token); + + return ( + indentation === desiredIndent || + // To avoid conflicts with no-mixed-spaces-and-tabs, don't report mixed spaces and tabs. + (indentation.includes(' ') && indentation.includes('\t')) + ); + } + + /** + * Check to see if the node is a file level IIFE + * @param node The function node to check. + * @returns True if the node is the outer IIFE + */ + function isOuterIIFE(node: TSESTree.Node): boolean { + /* + * Verify that the node is an IIFE + */ + if ( + !node.parent || + node.parent.type !== 'CallExpression' || + node.parent.callee !== node + ) { + return false; + } + + /* + * Navigate legal ancestors to determine whether this IIFE is outer. + * A "legal ancestor" is an expression or statement that causes the function to get executed immediately. + * For example, `!(function(){})()` is an outer IIFE even though it is preceded by a ! operator. + */ + let statement = node.parent && node.parent.parent; + + while ( + statement && + ((statement.type === AST_NODE_TYPES.UnaryExpression && + ['!', '~', '+', '-'].indexOf(statement.operator) > -1) || + statement.type === AST_NODE_TYPES.AssignmentExpression || + statement.type === AST_NODE_TYPES.LogicalExpression || + statement.type === AST_NODE_TYPES.SequenceExpression || + statement.type === AST_NODE_TYPES.VariableDeclarator) + ) { + statement = statement.parent; + } + + return ( + !!statement && + (statement.type === AST_NODE_TYPES.ExpressionStatement || + statement.type === AST_NODE_TYPES.VariableDeclaration) && + !!statement.parent && + statement.parent.type === AST_NODE_TYPES.Program + ); + } + + /** + * Counts the number of linebreaks that follow the last non-whitespace character in a string + * @param str The string to check + * @returns The number of JavaScript linebreaks that follow the last non-whitespace character, + * or the total number of linebreaks if the string is all whitespace. + */ + function countTrailingLinebreaks(str: string): number { + const trailingWhitespace = str.match(/\s*$/u)![0]; + const linebreakMatches = trailingWhitespace.match( + createGlobalLinebreakMatcher(), + ); + + return linebreakMatches === null ? 0 : linebreakMatches.length; + } + + /** + * Check indentation for lists of elements (arrays, objects, function params) + * @param elements List of elements that should be offset + * @param startToken The start token of the list that element should be aligned against, e.g. '[' + * @param endToken The end token of the list, e.g. ']' + * @param offset The amount that the elements should be offset + */ + function addElementListIndent( + elements: TSESTree.Node[], + startToken: TSESTree.Token, + endToken: TSESTree.Token, + offset: number | string, + ) { + /** + * Gets the first token of a given element, including surrounding parentheses. + * @param element A node in the `elements` list + * @returns The first token of this element + */ + function getFirstToken(element: TSESTree.Node): TSESTree.Token { + let token = sourceCode.getTokenBefore(element)!; + + while (isOpeningParenToken(token) && token !== startToken) { + token = sourceCode.getTokenBefore(token)!; + } + return sourceCode.getTokenAfter(token)!; + } + + // Run through all the tokens in the list, and offset them by one indent level (mainly for comments, other things will end up overridden) + offsets.setDesiredOffsets( + [startToken.range[1], endToken.range[0]], + startToken, + typeof offset === 'number' ? offset : 1, + ); + offsets.setDesiredOffset(endToken, startToken, 0); + + // If the preference is "first" but there is no first element (e.g. sparse arrays w/ empty first slot), fall back to 1 level. + if (offset === 'first' && elements.length && !elements[0]) { + return; + } + elements.forEach((element, index) => { + if (!element) { + // Skip holes in arrays + return; + } + if (offset === 'off') { + // Ignore the first token of every element if the "off" option is used + offsets.ignoreToken(getFirstToken(element)); + } + + // Offset the following elements correctly relative to the first element + if (index === 0) { + return; + } + if ( + offset === 'first' && + tokenInfo.isFirstTokenOfLine(getFirstToken(element)) + ) { + offsets.matchOffsetOf( + getFirstToken(elements[0]), + getFirstToken(element), + ); + } else { + const previousElement = elements[index - 1]; + const firstTokenOfPreviousElement = + previousElement && getFirstToken(previousElement); + const previousElementLastToken = + previousElement && sourceCode.getLastToken(previousElement)!; + + if ( + previousElement && + previousElementLastToken.loc.end.line - + countTrailingLinebreaks(previousElementLastToken.value) > + startToken.loc.end.line + ) { + offsets.setDesiredOffsets( + [previousElement.range[1], element.range[1]], + firstTokenOfPreviousElement, + 0, + ); + } + } + }); + } + + /** + * Check and decide whether to check for indentation for blockless nodes + * Scenarios are for or while statements without braces around them + */ + function addBlocklessNodeIndent(node: TSESTree.Node): void { + if (node.type !== 'BlockStatement') { + const lastParentToken = sourceCode.getTokenBefore( + node, + isNotOpeningParenToken, + )!; + + let firstBodyToken = sourceCode.getFirstToken(node)!; + let lastBodyToken = sourceCode.getLastToken(node)!; + + while ( + isOpeningParenToken(sourceCode.getTokenBefore(firstBodyToken)!) && + isClosingParenToken(sourceCode.getTokenAfter(lastBodyToken)!) + ) { + firstBodyToken = sourceCode.getTokenBefore(firstBodyToken)!; + lastBodyToken = sourceCode.getTokenAfter(lastBodyToken)!; + } + + offsets.setDesiredOffsets( + [firstBodyToken.range[0], lastBodyToken.range[1]], + lastParentToken, + 1, + ); + + /* + * For blockless nodes with semicolon-first style, don't indent the semicolon. + * e.g. + * if (foo) bar() + * ; [1, 2, 3].map(foo) + */ + const lastToken = sourceCode.getLastToken(node); + + if ( + lastToken && + node.type !== 'EmptyStatement' && + isSemicolonToken(lastToken) + ) { + offsets.setDesiredOffset(lastToken, lastParentToken, 0); + } + } + } + + /** + * Checks the indentation for nodes that are like function calls + */ + function addFunctionCallIndent( + node: TSESTree.CallExpression | TSESTree.NewExpression, + ): void { + const openingParen = node.arguments.length + ? sourceCode.getFirstTokenBetween( + node.callee, + node.arguments[0], + isOpeningParenToken, + )! + : sourceCode.getLastToken(node, 1)!; + const closingParen = sourceCode.getLastToken(node)!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + offsets.setDesiredOffset( + openingParen, + sourceCode.getTokenBefore(openingParen)!, + 0, + ); + + addElementListIndent( + node.arguments, + openingParen, + closingParen, + options.CallExpression.arguments!, + ); + } + + /** + * Checks the indentation of parenthesized values, given a list of tokens in a program + * @param tokens A list of tokens + */ + function addParensIndent(tokens: TSESTree.Token[]): void { + const parenStack: TSESTree.Token[] = []; + const parenPairs: { left: TSESTree.Token; right: TSESTree.Token }[] = []; + + tokens.forEach(nextToken => { + // Accumulate a list of parenthesis pairs + if (isOpeningParenToken(nextToken)) { + parenStack.push(nextToken); + } else if (isClosingParenToken(nextToken)) { + parenPairs.unshift({ left: parenStack.pop()!, right: nextToken }); + } + }); + + parenPairs.forEach(pair => { + const leftParen = pair.left; + const rightParen = pair.right; + + // We only want to handle parens around expressions, so exclude parentheses that are in function parameters and function call arguments. + if ( + !parameterParens.has(leftParen) && + !parameterParens.has(rightParen) + ) { + const parenthesizedTokens = new Set( + sourceCode.getTokensBetween(leftParen, rightParen), + ); + + parenthesizedTokens.forEach(token => { + if (!parenthesizedTokens.has(offsets.getFirstDependency(token)!)) { + offsets.setDesiredOffset(token, leftParen, 1); + } + }); + } + + offsets.setDesiredOffset(rightParen, leftParen, 0); + }); + } + + /** + * Ignore all tokens within an unknown node whose offset do not depend + * on another token's offset within the unknown node + */ + function ignoreNode(node: TSESTree.Node): void { + const unknownNodeTokens = new Set( + sourceCode.getTokens(node, { includeComments: true }), + ); + + unknownNodeTokens.forEach(token => { + if (!unknownNodeTokens.has(offsets.getFirstDependency(token)!)) { + const firstTokenOfLine = tokenInfo.getFirstTokenOfLine(token); + + if (token === firstTokenOfLine) { + offsets.ignoreToken(token); + } else { + offsets.setDesiredOffset(token, firstTokenOfLine, 0); + } + } + }); + } + + /** + * Check whether the given token is on the first line of a statement. + * @param leafNode The expression node that the token belongs directly. + * @returns `true` if the token is on the first line of a statement. + */ + function isOnFirstLineOfStatement( + token: TSESTree.Token, + leafNode: TSESTree.Node, + ): boolean { + let node: TSESTree.Node | undefined = leafNode; + + while ( + node.parent && + !node.parent.type.endsWith('Statement') && + !node.parent.type.endsWith('Declaration') + ) { + node = node.parent; + } + node = node.parent; + + return !node || node.loc.start.line === token.loc.start.line; + } + + /** + * Check whether there are any blank (whitespace-only) lines between + * two tokens on separate lines. + * @returns `true` if the tokens are on separate lines and + * there exists a blank line between them, `false` otherwise. + */ + function hasBlankLinesBetween( + firstToken: TSESTree.Token, + secondToken: TSESTree.Token, + ): boolean { + const firstTokenLine = firstToken.loc.end.line; + const secondTokenLine = secondToken.loc.start.line; + + if ( + firstTokenLine === secondTokenLine || + firstTokenLine === secondTokenLine - 1 + ) { + return false; + } + + for (let line = firstTokenLine + 1; line < secondTokenLine; ++line) { + if (!tokenInfo.firstTokensByLineNumber.has(line)) { + return true; + } + } + + return false; + } + + const ignoredNodeFirstTokens = new Set(); + + const baseOffsetListeners: RuleListener = { + 'ArrayExpression, ArrayPattern'( + node: TSESTree.ArrayExpression | TSESTree.ArrayPattern, + ) { + const openingBracket = sourceCode.getFirstToken(node)!; + const closingBracket = sourceCode.getTokenAfter( + node.elements[node.elements.length - 1] || openingBracket, + isClosingBracketToken, + )!; + + addElementListIndent( + node.elements, + openingBracket, + closingBracket, + options.ArrayExpression, + ); + }, + + ArrowFunctionExpression(node) { + const firstToken = sourceCode.getFirstToken(node)!; + + if (isOpeningParenToken(firstToken)) { + const openingParen = firstToken; + const closingParen = sourceCode.getTokenBefore( + node.body, + isClosingParenToken, + )!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options.FunctionExpression.parameters!, + ); + } + addBlocklessNodeIndent(node.body); + }, + + AssignmentExpression(node) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + )!; + + offsets.setDesiredOffsets( + [operator.range[0], node.range[1]], + sourceCode.getLastToken(node.left)!, + 1, + ); + offsets.ignoreToken(operator); + offsets.ignoreToken(sourceCode.getTokenAfter(operator)!); + }, + + 'BinaryExpression, LogicalExpression'( + node: TSESTree.BinaryExpression | TSESTree.LogicalExpression, + ) { + const operator = sourceCode.getFirstTokenBetween( + node.left, + node.right, + token => token.value === node.operator, + )!; + + /* + * For backwards compatibility, don't check BinaryExpression indents, e.g. + * var foo = bar && + * baz; + */ + + const tokenAfterOperator = sourceCode.getTokenAfter(operator)!; + + offsets.ignoreToken(operator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffset(tokenAfterOperator, operator, 0); + }, + + 'BlockStatement, ClassBody'( + node: TSESTree.BlockStatement | TSESTree.ClassBody, + ) { + let blockIndentLevel; + + if (node.parent && isOuterIIFE(node.parent)) { + blockIndentLevel = options.outerIIFEBody; + } else if ( + node.parent && + (node.parent.type === AST_NODE_TYPES.FunctionExpression || + node.parent.type === AST_NODE_TYPES.ArrowFunctionExpression) + ) { + blockIndentLevel = options.FunctionExpression.body; + } else if ( + node.parent && + node.parent.type === AST_NODE_TYPES.FunctionDeclaration + ) { + blockIndentLevel = options.FunctionDeclaration.body; + } else { + blockIndentLevel = 1; + } + + /* + * For blocks that aren't lone statements, ensure that the opening curly brace + * is aligned with the parent. + */ + if (node.parent && !STATEMENT_LIST_PARENTS.has(node.parent.type)) { + offsets.setDesiredOffset( + sourceCode.getFirstToken(node)!, + sourceCode.getFirstToken(node.parent)!, + 0, + ); + } + addElementListIndent( + node.body, + sourceCode.getFirstToken(node)!, + sourceCode.getLastToken(node)!, + blockIndentLevel!, + ); + }, + + CallExpression: addFunctionCallIndent, + + 'ClassDeclaration[superClass], ClassExpression[superClass]'( + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, + ) { + const classToken = sourceCode.getFirstToken(node)!; + const extendsToken = sourceCode.getTokenBefore( + node.superClass!, + isNotOpeningParenToken, + )!; + + offsets.setDesiredOffsets( + [extendsToken.range[0], node.body.range[0]], + classToken, + 1, + ); + }, + + ConditionalExpression(node) { + const firstToken = sourceCode.getFirstToken(node)!; + + // `flatTernaryExpressions` option is for the following style: + // var a = + // foo > 0 ? bar : + // foo < 0 ? baz : + // /*else*/ qiz ; + if ( + !options.flatTernaryExpressions || + node.test.loc.end.line !== node.consequent.loc.start.line || + isOnFirstLineOfStatement(firstToken, node) + ) { + const questionMarkToken = sourceCode.getFirstTokenBetween( + node.test, + node.consequent, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '?', + )!; + const colonToken = sourceCode.getFirstTokenBetween( + node.consequent, + node.alternate, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === ':', + )!; + + const firstConsequentToken = sourceCode.getTokenAfter( + questionMarkToken, + )!; + const lastConsequentToken = sourceCode.getTokenBefore(colonToken)!; + const firstAlternateToken = sourceCode.getTokenAfter(colonToken)!; + + offsets.setDesiredOffset(questionMarkToken, firstToken, 1); + offsets.setDesiredOffset(colonToken, firstToken, 1); + + offsets.setDesiredOffset(firstConsequentToken, firstToken, 1); + + /* + * The alternate and the consequent should usually have the same indentation. + * If they share part of a line, align the alternate against the first token of the consequent. + * This allows the alternate to be indented correctly in cases like this: + * foo ? ( + * bar + * ) : ( // this '(' is aligned with the '(' above, so it's considered to be aligned with `foo` + * baz // as a result, `baz` is offset by 1 rather than 2 + * ) + */ + if ( + lastConsequentToken.loc.end.line === + firstAlternateToken.loc.start.line + ) { + offsets.setDesiredOffset( + firstAlternateToken, + firstConsequentToken, + 0, + ); + } else { + /** + * If the alternate and consequent do not share part of a line, offset the alternate from the first + * token of the conditional expression. For example: + * foo ? bar + * : baz + * + * If `baz` were aligned with `bar` rather than being offset by 1 from `foo`, `baz` would end up + * having no expected indentation. + */ + offsets.setDesiredOffset(firstAlternateToken, firstToken, 1); + } + } + }, + + 'DoWhileStatement, WhileStatement, ForInStatement, ForOfStatement': ( + node: + | TSESTree.DoWhileStatement + | TSESTree.WhileStatement + | TSESTree.ForInStatement + | TSESTree.ForOfStatement, + ) => { + addBlocklessNodeIndent(node.body); + }, + + ExportNamedDeclaration(node) { + if (node.declaration === null) { + const closingCurly = sourceCode.getLastToken( + node, + isClosingBraceToken, + )!; + + // Indent the specifiers in `export {foo, bar, baz}` + addElementListIndent( + node.specifiers, + sourceCode.getFirstToken(node, { skip: 1 })!, + closingCurly, + 1, + ); + + if (node.source) { + // Indent everything after and including the `from` token in `export {foo, bar, baz} from 'qux'` + offsets.setDesiredOffsets( + [closingCurly.range[1], node.range[1]], + sourceCode.getFirstToken(node)!, + 1, + ); + } + } + }, + + ForStatement(node) { + const forOpeningParen = sourceCode.getFirstToken(node, 1)!; + + if (node.init) { + offsets.setDesiredOffsets(node.init.range, forOpeningParen, 1); + } + if (node.test) { + offsets.setDesiredOffsets(node.test.range, forOpeningParen, 1); + } + if (node.update) { + offsets.setDesiredOffsets(node.update.range, forOpeningParen, 1); + } + addBlocklessNodeIndent(node.body); + }, + + 'FunctionDeclaration, FunctionExpression'( + node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, + ) { + const closingParen = sourceCode.getTokenBefore(node.body!)!; + const openingParen = sourceCode.getTokenBefore( + node.params.length ? node.params[0] : closingParen, + )!; + + parameterParens.add(openingParen); + parameterParens.add(closingParen); + addElementListIndent( + node.params, + openingParen, + closingParen, + options[node.type].parameters!, + ); + }, + + IfStatement(node) { + addBlocklessNodeIndent(node.consequent); + if (node.alternate && node.alternate.type !== 'IfStatement') { + addBlocklessNodeIndent(node.alternate); + } + }, + + ImportDeclaration(node) { + if ( + node.specifiers.some( + specifier => specifier.type === 'ImportSpecifier', + ) + ) { + const openingCurly = sourceCode.getFirstToken( + node, + isOpeningBraceToken, + )!; + const closingCurly = sourceCode.getLastToken( + node, + isClosingBraceToken, + )!; + + addElementListIndent( + node.specifiers.filter( + specifier => specifier.type === 'ImportSpecifier', + ), + openingCurly, + closingCurly, + options.ImportDeclaration, + ); + } + + const fromToken = sourceCode.getLastToken( + node, + token => token.type === 'Identifier' && token.value === 'from', + )!; + const sourceToken = sourceCode.getLastToken( + node, + token => token.type === 'String', + )!; + const semiToken = sourceCode.getLastToken( + node, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === ';', + )!; + + if (fromToken) { + const end = + semiToken && semiToken.range[1] === sourceToken.range[1] + ? node.range[1] + : sourceToken.range[1]; + + offsets.setDesiredOffsets( + [fromToken.range[0], end], + sourceCode.getFirstToken(node)!, + 1, + ); + } + }, + + 'MemberExpression, JSXMemberExpression, MetaProperty'( + node: + | TSESTree.MemberExpression + | TSESTree.JSXMemberExpression + | TSESTree.MetaProperty, + ) { + const object = + node.type === AST_NODE_TYPES.MetaProperty ? node.meta : node.object; + const isComputed = 'computed' in node && node.computed; + const firstNonObjectToken = sourceCode.getFirstTokenBetween( + object, + node.property, + isNotClosingParenToken, + )!; + const secondNonObjectToken = sourceCode.getTokenAfter( + firstNonObjectToken, + )!; + + const objectParenCount = sourceCode.getTokensBetween( + object, + node.property, + { filter: isClosingParenToken }, + ).length; + const firstObjectToken = objectParenCount + ? sourceCode.getTokenBefore(object, { skip: objectParenCount - 1 })! + : sourceCode.getFirstToken(object)!; + const lastObjectToken = sourceCode.getTokenBefore(firstNonObjectToken)!; + const firstPropertyToken = isComputed + ? firstNonObjectToken + : secondNonObjectToken; + + if (isComputed) { + // For computed MemberExpressions, match the closing bracket with the opening bracket. + offsets.setDesiredOffset( + sourceCode.getLastToken(node)!, + firstNonObjectToken, + 0, + ); + offsets.setDesiredOffsets( + node.property.range, + firstNonObjectToken, + 1, + ); + } + + /* + * If the object ends on the same line that the property starts, match against the last token + * of the object, to ensure that the MemberExpression is not indented. + * + * Otherwise, match against the first token of the object, e.g. + * foo + * .bar + * .baz // <-- offset by 1 from `foo` + */ + const offsetBase = + lastObjectToken.loc.end.line === firstPropertyToken.loc.start.line + ? lastObjectToken + : firstObjectToken; + + if (typeof options.MemberExpression === 'number') { + // Match the dot (for non-computed properties) or the opening bracket (for computed properties) against the object. + offsets.setDesiredOffset( + firstNonObjectToken, + offsetBase, + options.MemberExpression, + ); + + /* + * For computed MemberExpressions, match the first token of the property against the opening bracket. + * Otherwise, match the first token of the property against the object. + */ + offsets.setDesiredOffset( + secondNonObjectToken, + isComputed ? firstNonObjectToken : offsetBase, + options.MemberExpression, + ); + } else { + // If the MemberExpression option is off, ignore the dot and the first token of the property. + offsets.ignoreToken(firstNonObjectToken); + offsets.ignoreToken(secondNonObjectToken); + + // To ignore the property indentation, ensure that the property tokens depend on the ignored tokens. + offsets.setDesiredOffset(firstNonObjectToken, offsetBase, 0); + offsets.setDesiredOffset( + secondNonObjectToken, + firstNonObjectToken, + 0, + ); + } + }, + + NewExpression(node) { + // Only indent the arguments if the NewExpression has parens (e.g. `new Foo(bar)` or `new Foo()`, but not `new Foo` + if ( + node.arguments.length > 0 || + (isClosingParenToken(sourceCode.getLastToken(node)!) && + isOpeningParenToken(sourceCode.getLastToken(node, 1)!)) + ) { + addFunctionCallIndent(node); + } + }, + + 'ObjectExpression, ObjectPattern'( + node: TSESTree.ObjectExpression | TSESTree.ObjectPattern, + ) { + const openingCurly = sourceCode.getFirstToken(node)!; + const closingCurly = sourceCode.getTokenAfter( + node.properties.length + ? node.properties[node.properties.length - 1] + : openingCurly, + isClosingBraceToken, + )!; + + addElementListIndent( + node.properties, + openingCurly, + closingCurly, + options.ObjectExpression, + ); + }, + + Property(node) { + if (!node.shorthand && !node.method && node.kind === 'init') { + const colon = sourceCode.getFirstTokenBetween( + node.key, + node.value, + isColonToken, + )!; + + offsets.ignoreToken(sourceCode.getTokenAfter(colon)!); + } + }, + + SwitchStatement(node) { + const openingCurly = sourceCode.getTokenAfter( + node.discriminant, + isOpeningBraceToken, + )!; + const closingCurly = sourceCode.getLastToken(node)!; + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + options.SwitchCase, + ); + + if (node.cases.length) { + sourceCode + .getTokensBetween(node.cases[node.cases.length - 1], closingCurly, { + includeComments: true, + filter: isCommentToken, + }) + .forEach(token => offsets.ignoreToken(token)); + } + }, + + SwitchCase(node) { + if ( + !( + node.consequent.length === 1 && + node.consequent[0].type === 'BlockStatement' + ) + ) { + const caseKeyword = sourceCode.getFirstToken(node)!; + const tokenAfterCurrentCase = sourceCode.getTokenAfter(node)!; + + offsets.setDesiredOffsets( + [caseKeyword.range[1], tokenAfterCurrentCase.range[0]], + caseKeyword, + 1, + ); + } + }, + + TemplateLiteral(node) { + node.expressions.forEach((_, index) => { + const previousQuasi = node.quasis[index]; + const nextQuasi = node.quasis[index + 1]; + const tokenToAlignFrom = + previousQuasi.loc.start.line === previousQuasi.loc.end.line + ? sourceCode.getFirstToken(previousQuasi) + : null; + + offsets.setDesiredOffsets( + [previousQuasi.range[1], nextQuasi.range[0]], + tokenToAlignFrom, + 1, + ); + offsets.setDesiredOffset( + sourceCode.getFirstToken(nextQuasi)!, + tokenToAlignFrom, + 0, + ); + }); + }, + + VariableDeclaration(node) { + let variableIndent = Object.prototype.hasOwnProperty.call( + options.VariableDeclarator, + node.kind, + ) + ? (options.VariableDeclarator as VariableDeclaratorObj)[node.kind] + : DEFAULT_VARIABLE_INDENT; + + const firstToken = sourceCode.getFirstToken(node)!; + const lastToken = sourceCode.getLastToken(node)!; + + if (variableIndent === 'first') { + if (node.declarations.length > 1) { + addElementListIndent( + node.declarations, + firstToken, + lastToken, + 'first', + ); + return; + } + + variableIndent = DEFAULT_VARIABLE_INDENT; + } + + if ( + node.declarations[node.declarations.length - 1].loc.start.line > + node.loc.start.line + ) { + /* + * VariableDeclarator indentation is a bit different from other forms of indentation, in that the + * indentation of an opening bracket sometimes won't match that of a closing bracket. For example, + * the following indentations are correct: + * + * var foo = { + * ok: true + * }; + * + * var foo = { + * ok: true, + * }, + * bar = 1; + * + * Account for when exiting the AST (after indentations have already been set for the nodes in + * the declaration) by manually increasing the indentation level of the tokens in this declarator + * on the same line as the start of the declaration, provided that there are declarators that + * follow this one. + */ + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent as number, + true, + ); + } else { + offsets.setDesiredOffsets( + node.range, + firstToken, + variableIndent as number, + ); + } + + if (isSemicolonToken(lastToken)) { + offsets.ignoreToken(lastToken); + } + }, + + VariableDeclarator(node) { + if (node.init) { + const equalOperator = sourceCode.getTokenBefore( + node.init, + isNotOpeningParenToken, + )!; + const tokenAfterOperator = sourceCode.getTokenAfter(equalOperator)!; + + offsets.ignoreToken(equalOperator); + offsets.ignoreToken(tokenAfterOperator); + offsets.setDesiredOffsets( + [tokenAfterOperator.range[0], node.range[1]], + equalOperator, + 1, + ); + offsets.setDesiredOffset( + equalOperator, + sourceCode.getLastToken(node.id), + 0, + ); + } + }, + + 'JSXAttribute[value]'(node: TSESTree.JSXAttribute) { + const nodeValue = node.value!; + const equalsToken = sourceCode.getFirstTokenBetween( + node.name, + nodeValue, + token => + token.type === AST_TOKEN_TYPES.Punctuator && token.value === '=', + )!; + + offsets.setDesiredOffsets( + [equalsToken.range[0], nodeValue.range[1]], + sourceCode.getFirstToken(node.name), + 1, + ); + }, + + JSXElement(node) { + if (node.closingElement) { + addElementListIndent( + node.children, + sourceCode.getFirstToken(node.openingElement)!, + sourceCode.getFirstToken(node.closingElement)!, + 1, + ); + } + }, + + JSXOpeningElement(node) { + const firstToken = sourceCode.getFirstToken(node)!; + let closingToken; + + if (node.selfClosing) { + closingToken = sourceCode.getLastToken(node, { skip: 1 })!; + offsets.setDesiredOffset( + sourceCode.getLastToken(node)!, + closingToken, + 0, + ); + } else { + closingToken = sourceCode.getLastToken(node)!; + } + offsets.setDesiredOffsets( + node.name.range, + sourceCode.getFirstToken(node)!, + ); + addElementListIndent(node.attributes, firstToken, closingToken, 1); + }, + + JSXClosingElement(node) { + const firstToken = sourceCode.getFirstToken(node); + + offsets.setDesiredOffsets(node.name.range, firstToken, 1); + }, + + JSXExpressionContainer(node) { + const openingCurly = sourceCode.getFirstToken(node)!; + const closingCurly = sourceCode.getLastToken(node)!; + + offsets.setDesiredOffsets( + [openingCurly.range[1], closingCurly.range[0]], + openingCurly, + 1, + ); + }, + + '*'(node: TSESTree.Node) { + const firstToken = sourceCode.getFirstToken(node); + + // Ensure that the children of every node are indented at least as much as the first token. + if (firstToken && !ignoredNodeFirstTokens.has(firstToken)) { + offsets.setDesiredOffsets(node.range, firstToken, 0); + } + }, + }; + + const listenerCallQueue: { + listener: RuleFunction; + node: TSESTree.Node; + }[] = []; + + /* + * To ignore the indentation of a node: + * 1. Don't call the node's listener when entering it (if it has a listener) + * 2. Don't set any offsets against the first token of the node. + * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. + */ + const offsetListeners = Object.keys(baseOffsetListeners).reduce< + RuleListener + >( + /* + * Offset listener calls are deferred until traversal is finished, and are called as + * part of the final `Program:exit` listener. This is necessary because a node might + * be matched by multiple selectors. + * + * Example: Suppose there is an offset listener for `Identifier`, and the user has + * specified in configuration that `MemberExpression > Identifier` should be ignored. + * Due to selector specificity rules, the `Identifier` listener will get called first. However, + * if a given Identifier node is supposed to be ignored, then the `Identifier` offset listener + * should not have been called at all. Without doing extra selector matching, we don't know + * whether the Identifier matches the `MemberExpression > Identifier` selector until the + * `MemberExpression > Identifier` listener is called. + * + * To avoid this, the `Identifier` listener isn't called until traversal finishes and all + * ignored nodes are known. + */ + (acc, key) => { + const listener = baseOffsetListeners[key] as RuleFunction< + TSESTree.Node + >; + acc[key] = node => listenerCallQueue.push({ listener, node }); + + return acc; + }, + {}, + ); + + // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. + const ignoredNodes = new Set(); + + /** + * Ignores a node + * @param node The node to ignore + */ + function addToIgnoredNodes(node: TSESTree.Node): void { + ignoredNodes.add(node); + ignoredNodeFirstTokens.add(sourceCode.getFirstToken(node)); + } + + const ignoredNodeListeners = options.ignoredNodes.reduce( + (listeners, ignoredSelector) => + Object.assign(listeners, { [ignoredSelector]: addToIgnoredNodes }), + {}, + ); + + /* + * Join the listeners, and add a listener to verify that all tokens actually have the correct indentation + * at the end. + * + * Using Object.assign will cause some offset listeners to be overwritten if the same selector also appears + * in `ignoredNodeListeners`. This isn't a problem because all of the matching nodes will be ignored, + * so those listeners wouldn't be called anyway. + */ + return Object.assign(offsetListeners, ignoredNodeListeners, { + '*:exit'(node: TSESTree.Node) { + // If a node's type is nonstandard, we can't tell how its children should be offset, so ignore it. + if (!KNOWN_NODES.has(node.type)) { + addToIgnoredNodes(node); + } + }, + 'Program:exit'() { + // If ignoreComments option is enabled, ignore all comment tokens. + if (options.ignoreComments) { + sourceCode + .getAllComments() + .forEach(comment => offsets.ignoreToken(comment)); + } + + // Invoke the queued offset listeners for the nodes that aren't ignored. + listenerCallQueue + .filter(nodeInfo => !ignoredNodes.has(nodeInfo.node)) + .forEach(nodeInfo => nodeInfo.listener(nodeInfo.node)); + + // Update the offsets for ignored nodes to prevent their child tokens from being reported. + ignoredNodes.forEach(ignoreNode); + + addParensIndent(sourceCode.ast.tokens); + + /* + * Create a Map from (tokenOrComment) => (precedingToken). + * This is necessary because sourceCode.getTokenBefore does not handle a comment as an argument correctly. + */ + const precedingTokens = sourceCode.ast.comments.reduce( + (commentMap, comment) => { + const tokenOrCommentBefore = sourceCode.getTokenBefore(comment, { + includeComments: true, + })!; + + return commentMap.set( + comment, + commentMap.has(tokenOrCommentBefore) + ? commentMap.get(tokenOrCommentBefore) + : tokenOrCommentBefore, + ); + }, + new WeakMap(), + ); + + sourceCode.lines.forEach((_, lineIndex) => { + const lineNumber = lineIndex + 1; + + if (!tokenInfo.firstTokensByLineNumber.has(lineNumber)) { + // Don't check indentation on blank lines + return; + } + + const firstTokenOfLine = tokenInfo.firstTokensByLineNumber.get( + lineNumber, + )!; + + if (firstTokenOfLine.loc.start.line !== lineNumber) { + // Don't check the indentation of multi-line tokens (e.g. template literals or block comments) twice. + return; + } + + // If the token matches the expected expected indentation, don't report it. + if ( + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(firstTokenOfLine), + ) + ) { + return; + } + + if (isCommentToken(firstTokenOfLine)) { + const tokenBefore = precedingTokens.get(firstTokenOfLine); + const tokenAfter = tokenBefore + ? sourceCode.getTokenAfter(tokenBefore)! + : sourceCode.ast.tokens[0]; + + const mayAlignWithBefore = + tokenBefore && + !hasBlankLinesBetween(tokenBefore, firstTokenOfLine); + const mayAlignWithAfter = + tokenAfter && !hasBlankLinesBetween(firstTokenOfLine, tokenAfter); + + // If a comment matches the expected indentation of the token immediately before or after, don't report it. + if ( + (mayAlignWithBefore && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenBefore), + )) || + (mayAlignWithAfter && + validateTokenIndent( + firstTokenOfLine, + offsets.getDesiredIndent(tokenAfter), + )) + ) { + return; + } + } + + // Otherwise, report the token/comment. + report(firstTokenOfLine, offsets.getDesiredIndent(firstTokenOfLine)); + }); + }, + }); + }, +}); diff --git a/packages/eslint-plugin/src/util/applyDefault.ts b/packages/eslint-plugin/src/util/applyDefault.ts index 56d513963a2..b54ca219864 100644 --- a/packages/eslint-plugin/src/util/applyDefault.ts +++ b/packages/eslint-plugin/src/util/applyDefault.ts @@ -19,7 +19,7 @@ export function applyDefault( } options.forEach((opt, i) => { - if (userOptions[i]) { + if (userOptions[i] !== undefined) { const userOpt = userOptions[i]; if (isObjectNotArray(userOpt) && isObjectNotArray(opt)) { diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 4e3d34937f9..07b6a0b3c3d 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -109,3 +109,12 @@ function keyCanBeReadAsPropertyName( node.type === AST_NODE_TYPES.Identifier ); } + +export type ExcludeKeys< + TObj extends Record, + TKeys extends keyof TObj +> = { [k in Exclude]: TObj[k] }; +export type RequireKeys< + TObj extends Record, + TKeys extends keyof TObj +> = ExcludeKeys & { [k in TKeys]-?: Exclude }; diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index d708a53cb4a..2703d00b5c2 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,5 +1,8 @@ import { ParserOptions } from '@typescript-eslint/parser'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; import { RuleTester as ESLintRuleTester } from 'eslint'; import * as path from 'path'; import RuleModule from 'ts-eslint'; @@ -28,7 +31,7 @@ interface InvalidTestCase< interface TestCaseError { messageId: TMessageIds; data?: Record; - type?: AST_NODE_TYPES; + type?: AST_NODE_TYPES | AST_TOKEN_TYPES; line?: number; column?: number; } diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js new file mode 100644 index 00000000000..f03507ff61e --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/indent/indent-invalid-fixture-1.js @@ -0,0 +1,530 @@ +if (a) { + var b = c; + var d = e + * f; + var e = f; // <- +// -> + function g() { + if (h) { + var i = j; + } // <- + } // <- + + while (k) l++; + while (m) { + n--; // -> + } // <- + + do { + o = p + + q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + o = p + + q; + } while(r); // <- + + for (var s in t) { + u++; + } + + for (;;) { + v++; // <- + } + + if ( w ) { + x++; + } else if (y) { + z++; // <- + aa++; + } else { // <- + bb++; // -> +} // -> +} + +/**/var b; // NO ERROR: single line multi-line comments followed by code is OK +/* + * + */ var b; // NO ERROR: multi-line comments followed by code is OK + +var arr = [ + a, + b, + c, + function (){ + d + }, // <- + {}, + { + a: b, + c: d, + d: e + }, + [ + f, + g, + h, + i + ], + [j] +]; + +var obj = { + a: { + b: { + c: d, + e: f, + g: h + + i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + } + }, + g: [ + h, + i, + j, + k + ] +}; + +var arrObject = {a:[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]}; + +var objArray = [{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}]; + +var arrArray = [[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]]; + +var objObject = {a:{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}}; + + +switch (a) { + case 'a': + var a = 'b'; // -> + break; + case 'b': + var a = 'b'; + break; + case 'c': + var a = 'b'; // <- + break; + case 'd': + var a = 'b'; + break; // -> + case 'f': + var a = 'b'; + break; + case 'g': { + var a = 'b'; + break; + } + case 'z': + default: + break; // <- +} + +a.b('hi') + .c(a.b()) // <- + .d(); // <- + +if ( a ) { + if ( b ) { +d.e(f) // -> + .g() // -> + .h(); // -> + + i.j(m) + .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + + n.o(p) // <- + .q() // <- + .r(); // <- + } +} + +var a = b, + c = function () { + h = i; // -> + j = k; + l = m; // <- + }, + e = { + f: g, + n: o, + p: q + }, + r = [ + s, + t, + u + ]; + +var a = function () { +b = c; // -> + d = e; + f = g; // <- +}; + +function c(a, b) { + if (a || (a && + b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + return d; + } +} + +if ( a + || b ) { +var x; // -> + var c, + d = function(a, + b) { // <- + a; // -> + b; + c; // <- + } +} + + +a({ + d: 1 +}); + +a( +1 +); + +a( + b({ + d: 1 + }) +); + +a( + b( + c({ + d: 1, + e: 1, + f: 1 + }) + ) +); + +a({ d: 1 }); + +aa( + b({ // NO ERROR: CallExpression args not linted by default + c: d, // -> + e: f, + f: g + }) // -> +); + +aaaaaa( + b, + c, + { + d: a + } +); + +a(b, c, + d, e, + f, g // NO ERROR: alignment of arguments of callExpression not checked + ); // <- + +a( + ); // <- + +aaaaaa( + b, + c, { + d: a + }, { + e: f + } +); + +a.b() + .c(function(){ + var a; + }).d.e; + +if (a == 'b') { + if (c && d) e = f + else g('h').i('j') +} + +a = function (b, c) { + return a(function () { + var d = e + var f = g + var h = i + + if (!j) k('l', (m = n)) + if (o) p + else if (q) r + }) +} + +var a = function() { + "b" + .replace(/a/, "a") + .replace(/bc?/, function(e) { + return "b" + (e.f === 2 ? "c" : "f"); + }) + .replace(/d/, "d"); +}; + +$(b) + .on('a', 'b', function() { $(c).e('f'); }) + .on('g', 'h', function() { $(i).j('k'); }); + +a + .b('c', + 'd'); // NO ERROR: CallExpression args not linted by default + +a + .b('c', [ 'd', function(e) { + e++; + }]); + +var a = function() { + a++; + b++; // <- + c++; // <- + }, + b; + +var b = [ + a, + b, + c + ], + c; + +var c = { + a: 1, + b: 2, + c: 3 + }, + d; + +// holes in arrays indentation +x = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 +]; + +try { + a++; + b++; // <- +c++; // -> +} catch (d) { + e++; + f++; // <- +g++; // -> +} finally { + h++; + i++; // <- +j++; // -> +} + +if (array.some(function(){ + return true; +})) { +a++; // -> + b++; + c++; // <- +} + +var a = b.c(function() { + d++; + }), + e; + +switch (true) { + case (a + && b): +case (c // -> +&& d): + case (e // <- + && f): + case (g +&& h): + var i = j; // <- + var k = l; + var m = n; // -> +} + +if (a) { + b(); +} +else { +c(); // -> + d(); + e(); // <- +} + +if (a) b(); +else { +c(); // -> + d(); + e(); // <- +} + +if (a) { + b(); +} else c(); + +if (a) { + b(); +} +else c(); + +a(); + +if( "very very long multi line" + + "with weird indentation" ) { + b(); +a(); // -> + c(); // <- +} + +a( "very very long multi line" + + "with weird indentation", function() { + b(); +a(); // -> + c(); // <- + }); // <- + +a = function(content, dom) { + b(); + c(); // <- +d(); // -> +}; + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> + }; + +a = function(content, dom) { + b(); // -> + }; + +a = function(content, dom) { +b(); // -> + }; + +a('This is a terribly long description youll ' + + 'have to read', function () { + b(); // <- + c(); // <- + }); // <- + +if ( + array.some(function(){ + return true; + }) +) { +a++; // -> + b++; + c++; // <- +} + +function c(d) { + return { + e: function(f, g) { + } + }; +} + +function a(b) { + switch(x) { + case 1: + if (foo) { + return 5; + } + } +} + +function a(b) { + switch(x) { + case 1: + c; + } +} + +function a(b) { + switch(x) { + case 1: c; + } +} + +function test() { + var a = 1; + { + a(); + } +} + +{ + a(); +} + +function a(b) { + switch(x) { + case 1: + { // <- + a(); // -> + } + break; + default: + { + b(); + } + } +} + +switch (a) { + default: + if (b) + c(); +} + +function test(x) { + switch (x) { + case 1: + return function() { + var a = 5; + return a; + }; + } +} + +switch (a) { + default: + if (b) + c(); +} diff --git a/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js b/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js new file mode 100644 index 00000000000..5c298429f69 --- /dev/null +++ b/packages/eslint-plugin/tests/fixtures/indent/indent-valid-fixture-1.js @@ -0,0 +1,530 @@ +if (a) { + var b = c; + var d = e + * f; + var e = f; // <- + // -> + function g() { + if (h) { + var i = j; + } // <- + } // <- + + while (k) l++; + while (m) { + n--; // -> + } // <- + + do { + o = p + + q; // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + o = p + + q; + } while(r); // <- + + for (var s in t) { + u++; + } + + for (;;) { + v++; // <- + } + + if ( w ) { + x++; + } else if (y) { + z++; // <- + aa++; + } else { // <- + bb++; // -> + } // -> +} + +/**/var b; // NO ERROR: single line multi-line comments followed by code is OK +/* + * + */ var b; // NO ERROR: multi-line comments followed by code is OK + +var arr = [ + a, + b, + c, + function (){ + d + }, // <- + {}, + { + a: b, + c: d, + d: e + }, + [ + f, + g, + h, + i + ], + [j] +]; + +var obj = { + a: { + b: { + c: d, + e: f, + g: h + + i // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + } + }, + g: [ + h, + i, + j, + k + ] +}; + +var arrObject = {a:[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]}; + +var objArray = [{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}]; + +var arrArray = [[ + a, + b, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c +]]; + +var objObject = {a:{ + a: b, + b: c, // NO ERROR: INDENT ONCE WHEN MULTIPLE INDENTED EXPRESSIONS ARE ON SAME LINE + c: d +}}; + + +switch (a) { + case 'a': + var a = 'b'; // -> + break; + case 'b': + var a = 'b'; + break; + case 'c': + var a = 'b'; // <- + break; + case 'd': + var a = 'b'; + break; // -> + case 'f': + var a = 'b'; + break; + case 'g': { + var a = 'b'; + break; + } + case 'z': + default: + break; // <- +} + +a.b('hi') + .c(a.b()) // <- + .d(); // <- + +if ( a ) { + if ( b ) { + d.e(f) // -> + .g() // -> + .h(); // -> + + i.j(m) + .k() // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + .l(); // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + + n.o(p) // <- + .q() // <- + .r(); // <- + } +} + +var a = b, + c = function () { + h = i; // -> + j = k; + l = m; // <- + }, + e = { + f: g, + n: o, + p: q + }, + r = [ + s, + t, + u + ]; + +var a = function () { + b = c; // -> + d = e; + f = g; // <- +}; + +function c(a, b) { + if (a || (a && + b)) { // NO ERROR: DON'T VALIDATE MULTILINE STATEMENTS + return d; + } +} + +if ( a + || b ) { + var x; // -> + var c, + d = function(a, + b) { // <- + a; // -> + b; + c; // <- + } +} + + +a({ + d: 1 +}); + +a( +1 +); + +a( + b({ + d: 1 + }) +); + +a( + b( + c({ + d: 1, + e: 1, + f: 1 + }) + ) +); + +a({ d: 1 }); + +aa( + b({ // NO ERROR: CallExpression args not linted by default + c: d, // -> + e: f, + f: g + }) // -> +); + +aaaaaa( + b, + c, + { + d: a + } +); + +a(b, c, + d, e, + f, g // NO ERROR: alignment of arguments of callExpression not checked +); // <- + +a( +); // <- + +aaaaaa( + b, + c, { + d: a + }, { + e: f + } +); + +a.b() + .c(function(){ + var a; + }).d.e; + +if (a == 'b') { + if (c && d) e = f + else g('h').i('j') +} + +a = function (b, c) { + return a(function () { + var d = e + var f = g + var h = i + + if (!j) k('l', (m = n)) + if (o) p + else if (q) r + }) +} + +var a = function() { + "b" + .replace(/a/, "a") + .replace(/bc?/, function(e) { + return "b" + (e.f === 2 ? "c" : "f"); + }) + .replace(/d/, "d"); +}; + +$(b) + .on('a', 'b', function() { $(c).e('f'); }) + .on('g', 'h', function() { $(i).j('k'); }); + +a + .b('c', + 'd'); // NO ERROR: CallExpression args not linted by default + +a + .b('c', [ 'd', function(e) { + e++; + }]); + +var a = function() { + a++; + b++; // <- + c++; // <- + }, + b; + +var b = [ + a, + b, + c + ], + c; + +var c = { + a: 1, + b: 2, + c: 3 + }, + d; + +// holes in arrays indentation +x = [ + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1, + 1 +]; + +try { + a++; + b++; // <- + c++; // -> +} catch (d) { + e++; + f++; // <- + g++; // -> +} finally { + h++; + i++; // <- + j++; // -> +} + +if (array.some(function(){ + return true; +})) { + a++; // -> + b++; + c++; // <- +} + +var a = b.c(function() { + d++; + }), + e; + +switch (true) { + case (a + && b): + case (c // -> +&& d): + case (e // <- + && f): + case (g +&& h): + var i = j; // <- + var k = l; + var m = n; // -> +} + +if (a) { + b(); +} +else { + c(); // -> + d(); + e(); // <- +} + +if (a) b(); +else { + c(); // -> + d(); + e(); // <- +} + +if (a) { + b(); +} else c(); + +if (a) { + b(); +} +else c(); + +a(); + +if( "very very long multi line" + + "with weird indentation" ) { + b(); + a(); // -> + c(); // <- +} + +a( "very very long multi line" + + "with weird indentation", function() { + b(); + a(); // -> + c(); // <- +}); // <- + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> +}; + +a = function(content, dom) { + b(); + c(); // <- + d(); // -> +}; + +a = function(content, dom) { + b(); // -> +}; + +a = function(content, dom) { + b(); // -> +}; + +a('This is a terribly long description youll ' + + 'have to read', function () { + b(); // <- + c(); // <- +}); // <- + +if ( + array.some(function(){ + return true; + }) +) { + a++; // -> + b++; + c++; // <- +} + +function c(d) { + return { + e: function(f, g) { + } + }; +} + +function a(b) { + switch(x) { + case 1: + if (foo) { + return 5; + } + } +} + +function a(b) { + switch(x) { + case 1: + c; + } +} + +function a(b) { + switch(x) { + case 1: c; + } +} + +function test() { + var a = 1; + { + a(); + } +} + +{ + a(); +} + +function a(b) { + switch(x) { + case 1: + { // <- + a(); // -> + } + break; + default: + { + b(); + } + } +} + +switch (a) { + default: + if (b) + c(); +} + +function test(x) { + switch (x) { + case 1: + return function() { + var a = 5; + return a; + }; + } +} + +switch (a) { + default: + if (b) + c(); +} diff --git a/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts new file mode 100644 index 00000000000..ea44a421249 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts @@ -0,0 +1,9646 @@ +// The following tests are adapted from the the tests in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +// NOTE - this test suite is intentionally kept in a separate file to our +// custom tests. This is to keep a clear boundary between the two. + +import { + AST_TOKEN_TYPES, + AST_NODE_TYPES, +} from '@typescript-eslint/typescript-estree'; +import fs from 'fs'; +import path from 'path'; +import rule from '../../../src/rules/indent-new-do-not-use'; +import { RuleTester } from '../../RuleTester'; +import { expectedErrors, unIndent } from './utils'; + +const fixture = fs.readFileSync( + path.join(__dirname, '../../fixtures/indent/indent-invalid-fixture-1.js'), + 'utf8', +); +const fixedFixture = fs.readFileSync( + path.join(__dirname, '../../fixtures/indent/indent-valid-fixture-1.js'), + 'utf8', +); + +const ruleTester = new RuleTester({ + parserOptions: { + ecmaVersion: 6, + sourceType: 'module', + ecmaFeatures: { + jsx: true, + }, + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('indent', rule, { + valid: [ + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', 'test23', function(responseData) { + window.ah.mobileAppVersion = responseData; + } + ); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', 'test23', function(responseData) { + window.ah.mobileAppVersion = responseData; + }); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', + null, + function responseCallback(responseData) { + window.ah.mobileAppVersion = responseData; + } + ); + `, + options: [2], + }, + { + code: unIndent` + bridge.callHandler( + 'getAppVersion', + null, + function responseCallback(responseData) { + window.ah.mobileAppVersion = responseData; + }); + `, + options: [2], + }, + { + code: unIndent` + function doStuff(keys) { + _.forEach( + keys, + key => { + doSomething(key); + } + ); + } + `, + options: [4], + }, + { + code: unIndent` + example( + function () { + console.log('example'); + } + ); + `, + options: [4], + }, + { + code: unIndent` + let foo = somethingList + .filter(x => { + return x; + }) + .map(x => { + return 100 * x; + }); + `, + options: [4], + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + options: [4], + }, + { + code: unIndent` + var x = 0 && + \t{ + \t\ta: 1, + \t\tb: 2 + \t}; + `, + options: ['tab'], + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }|| + { + c: 3, + d: 4 + }; + `, + options: [4], + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + }, + { + code: unIndent` + var x = ['a', + 'b', + 'c', + ]; + `, + options: [4], + }, + { + code: 'var x = 0 && 1;', + options: [4], + }, + { + code: 'var x = 0 && { a: 1, b: 2 };', + options: [4], + }, + { + code: unIndent` + var x = 0 && + ( + 1 + ); + `, + options: [4], + }, + { + code: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + options: [2], + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + // hi + }) + .then(function () { + return FunctionalHelpers.clearBrowserState(self, { + contentServer: true, + contentServer1: true + }); + }); + } + `, + options: [2], + }, + { + code: unIndent` + it('should... some lengthy test description that is forced to be' + + 'wrapped into two lines since the line length limit is set', () => { + expect(true).toBe(true); + }); + `, + options: [2], + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + var x = 1; + var y = 1; + }, function(err){ + var o = 1 - 2; + var y = 1 - 2; + return true; + }) + } + `, + options: [4], + }, + { + // https://github.com/eslint/eslint/issues/11802 + code: unIndent` + import foo from "foo" + + ;(() => {})() + `, + options: [4], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + function test() { + return client.signUp(email, PASSWORD, { preVerified: true }) + .then(function (result) { + var x = 1; + var y = 1; + }, function(err){ + var o = 1 - 2; + var y = 1 - 2; + return true; + }); + } + `, + options: [4, { MemberExpression: 0 }], + }, + + { + code: '// hi', + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var Command = function() { + var fileList = [], + files = [] + + files.concat(fileList) + }; + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: ' ', + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + if(data) { + console.log('hi'); + b = true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + foo = () => { + console.log('hi'); + return true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + function test(data) { + console.log('hi'); + return true;}; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var test = function(data) { + console.log('hi'); + }; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + arr.forEach(function(data) { + otherdata.forEach(function(zero) { + console.log('hi'); + }) }); + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + a = [ + ,3 + ] + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + [ + ['gzip', AST_TOKEN_TYPES.gunzip], + ['gzip', AST_TOKEN_TYPES.unzip], + ['deflate', AST_TOKEN_TYPES.inflate], + ['deflateRaw', AST_TOKEN_TYPES.inflateRaw], + ].forEach(function(method) { + console.log(method); + }); + `, + options: [2, { SwitchCase: 1, VariableDeclarator: 2 }], + }, + { + code: unIndent` + test(123, { + bye: { + hi: [1, + { + b: 2 + } + ] + } + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var xyz = 2, + lmn = [ + { + a: 1 + } + ]; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + lmnn = [{ + a: 1 + }, + { + b: 2 + }, { + x: 2 + }]; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + unIndent` + [{ + foo: 1 + }, { + foo: 2 + }, { + foo: 3 + }] + `, + unIndent` + foo([ + bar + ], [ + baz + ], [ + qux + ]); + `, + { + code: unIndent` + abc({ + test: [ + [ + c, + xyz, + 2 + ].join(',') + ] + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc = { + test: [ + [ + c, + xyz, + 2 + ] + ] + }; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc( + { + a: 1, + b: 2 + } + ); + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + abc({ + a: 1, + b: 2 + }); + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = + [ + c, + xyz, + { + a: 1, + b: 2 + } + ]; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = [ + c, + xyz, + { + a: 1, + b: 2 + } + ]; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` + var + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + const + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + let + x = { + a: 1, + }, + y = { + b: 2 + } + `, + unIndent` + var foo = { a: 1 }, bar = { + b: 2 + }; + `, + unIndent` + var foo = { a: 1 }, bar = { + b: 2 + }, + baz = { + c: 3 + } + `, + unIndent` + const { + foo + } = 1, + bar = 2 + `, + { + code: unIndent` + var foo = 1, + bar = + 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo = 1, + bar + = 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = 1, + bar + = 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = + 1, + bar + = + 2 + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var foo + = (1), + bar + = (2) + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let foo = 'foo', + bar = bar // <-- no semicolon here + const a = 'a', + b = 'b' // <-- no semicolon here + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + var foo = 1, + bar = 2, + baz = 3 + ; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` + var foo = 1, + bar = 2, + baz = 3 + ; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = bar; + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = 'bar' // <-- no semicolon here + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + }, + { + code: unIndent` + let foo = 1, + bar = 2, + baz + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let + foo + `, + options: [4, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + let foo = 1, + bar = + 2 + `, + options: [2, { VariableDeclarator: 'first' }], + }, + { + code: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = new abc({ + a: 1, + b: 2 + }), + b = 2; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = 2, + c = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var x = 2, + y = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var e = { + a: 1, + b: 2 + }, + b = 2; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function test() { + if (true || + false){ + console.log(val); + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + unIndent` + var foo = bar || + !( + baz + ); + `, + unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + { + code: unIndent` + for (var val in obj) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function hi(){ var a = 1; + y++; x++; + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + for(;length > index; index++)if(NO_HOLES || index in self){ + x++; + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var geometry = 2, + rotate = 2; + `, + options: [2, { VariableDeclarator: 0 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [4, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + \trotate; + `, + options: ['tab', { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 1 }], + }, + { + code: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + let geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + const geometry = 2, + rotate = 3; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: + 'var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth;', + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + if (1 < 2){ + //hi sd + } + `, + options: [2], + }, + { + code: unIndent` + while (1 < 2){ + //hi sd + } + `, + options: [2], + }, + { + code: "while (1 < 2) console.log('hi');", + options: [2], + }, + + { + code: unIndent` + [a, boop, + c].forEach((index) => { + index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, c].forEach((index) => { + index; + }); + `, + options: [4], + }, + { + code: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + options: [4], + }, + { + code: unIndent` + (foo) + .bar([ + baz + ]); + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + switch (x) { + case "foo": + a(); + break; + case "bar": + switch (y) { + case "1": + break; + case "2": + a = 6; + break; + } + case "test": + break; + } + `, + options: [4, { SwitchCase: 1 }], + }, + { + code: unIndent` + switch (x) { + case "foo": + a(); + break; + case "bar": + switch (y) { + case "1": + break; + case "2": + a = 6; + break; + } + case "test": + break; + } + `, + options: [4, { SwitchCase: 2 }], + }, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + switch(x){ + case '1': + break; + case '2': + a = 6; + break; + } + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + if(x){ + a = 2; + } + else{ + a = 6; + } + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + if(x){ + a = 2; + } + else + a = 6; + } + `, + unIndent` + switch (a) { + case "foo": + a(); + break; + case "bar": + a(); break; + case "baz": + a(); break; + } + `, + unIndent` + switch (0) { + } + `, + unIndent` + function foo() { + var a = "a"; + switch(a) { + case "a": + return "A"; + case "b": + return "B"; + } + } + foo(); + `, + { + code: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + a(); + break; + } + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + options: [4, { SwitchCase: 1 }], + }, + unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + unIndent` + if (a) { + (1 + 2 + 3); // no error on this line + } + `, + 'switch(value){ default: a(); break; }', + { + code: unIndent` + import {addons} from 'react/addons' + import React from 'react' + `, + options: [2], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var foo = 0, bar = 0; baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var a = 1, + b = 2, + c = 3; + `, + options: [4], + }, + { + code: unIndent` + var a = 1 + ,b = 2 + ,c = 3; + `, + options: [4], + }, + { + code: "while (1 < 2) console.log('hi')", + options: [2], + }, + { + code: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + var items = [ + { + foo: 'bar' + } + ]; + `, + options: [2, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + const a = 1, + b = 2; + const items1 = [ + { + foo: 'bar' + } + ]; + const items2 = Items( + { + foo: 'bar' + } + ); + `, + options: [2, { VariableDeclarator: 3 }], + }, + { + code: unIndent` + const geometry = 2, + rotate = 3; + var a = 1, + b = 2; + let light = true, + shadow = false; + `, + options: [2, { VariableDeclarator: { const: 3, let: 2 } }], + }, + { + code: unIndent` + const abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + let abc2 = 5, + c2 = 2, + xyz2 = + { + a: 1, + b: 2 + }; + var abc3 = 5, + c3 = 2, + xyz3 = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: { var: 2, const: 3 }, SwitchCase: 1 }], + }, + { + code: unIndent` + module.exports = { + 'Unit tests': + { + rootPath: './', + environment: 'node', + tests: + [ + 'test/test-*.js' + ], + sources: + [ + '*.js', + 'test/**.js' + ] + } + }; + `, + options: [2], + }, + { + code: unIndent` + foo = + bar; + `, + options: [2], + }, + { + code: unIndent` + foo = ( + bar + ); + `, + options: [2], + }, + { + code: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + options: [2], + }, + unIndent` + var a = 1 + ,b = 2 + ; + `, + { + code: unIndent` + export function create (some, + argument) { + return Object.create({ + a: some, + b: argument + }); + }; + `, + options: [2, { FunctionDeclaration: { parameters: 'first' } }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + export function create (id, xfilter, rawType, + width=defaultWidth, height=defaultHeight, + footerHeight=defaultFooterHeight, + padding=defaultPadding) { + // ... function body, indented two spaces + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first' } }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + var obj = { + foo: function () { + return new p() + .then(function (ok) { + return ok; + }, function () { + // ignore things + }); + } + }; + `, + options: [2], + }, + { + code: unIndent` + a.b() + .c(function(){ + var a; + }).d.e; + `, + options: [2], + }, + { + code: unIndent` + const YO = 'bah', + TE = 'mah' + + var res, + a = 5, + b = 4 + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` + const YO = 'bah', + TE = 'mah' + + var res, + a = 5, + b = 4 + + if (YO) console.log(TE) + `, + options: [2, { VariableDeclarator: { var: 2, let: 2, const: 3 } }], + }, + { + code: unIndent` + var foo = 'foo', + bar = 'bar', + baz = function() { + + } + + function hello () { + + } + `, + options: [2], + }, + { + code: unIndent` + var obj = { + send: function () { + return P.resolve({ + type: 'POST' + }) + .then(function () { + return true; + }, function () { + return false; + }); + } + }; + `, + options: [2], + }, + { + code: unIndent` + var obj = { + send: function () { + return P.resolve({ + type: 'POST' + }) + .then(function () { + return true; + }, function () { + return false; + }); + } + }; + `, + options: [2, { MemberExpression: 0 }], + }, + unIndent` + const someOtherFunction = argument => { + console.log(argument); + }, + someOtherValue = 'someOtherValue'; + `, + { + code: unIndent` + [ + 'a', + 'b' + ].sort().should.deepEqual([ + 'x', + 'y' + ]); + `, + options: [2], + }, + { + code: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = 1, + B = + class { + constructor(){} + a(){} + get b(){} + }, + c = 3; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + }, + { + code: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = { + some: 1 + , name: 2 + }; + `, + options: [2], + }, + { + code: unIndent` + a.c = { + aa: function() { + 'test1'; + return 'aa'; + } + , bb: function() { + return this.bb(); + } + }; + `, + options: [4], + }, + { + code: unIndent` + var a = + { + actions: + [ + { + name: 'compile' + } + ] + }; + `, + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + { + code: unIndent` + var a = + [ + { + name: 'compile' + } + ]; + `, + options: [4, { VariableDeclarator: 0, SwitchCase: 1 }], + }, + unIndent` + [[ + ], function( + foo + ) {} + ] + `, + unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + { + code: unIndent` + const func = function (opts) { + return Promise.resolve() + .then(() => { + [ + 'ONE', 'TWO' + ].forEach(command => { doSomething(); }); + }); + }; + `, + options: [4, { MemberExpression: 0 }], + }, + { + code: unIndent` + const func = function (opts) { + return Promise.resolve() + .then(() => { + [ + 'ONE', 'TWO' + ].forEach(command => { doSomething(); }); + }); + }; + `, + options: [4], + }, + { + code: unIndent` + var haveFun = function () { + SillyFunction( + { + value: true, + }, + { + _id: true, + } + ); + }; + `, + options: [4], + }, + { + code: unIndent` + var haveFun = function () { + new SillyFunction( + { + value: true, + }, + { + _id: true, + } + ); + }; + `, + options: [4], + }, + { + code: unIndent` + let object1 = { + doThing() { + return _.chain([]) + .map(v => ( + { + value: true, + } + )) + .value(); + } + }; + `, + options: [2], + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + options: [2], + }, + { + code: unIndent` + class Foo + extends Bar { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + class Foo extends + Bar { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + class Foo extends + ( + Bar + ) { + baz() {} + } + `, + options: [2], + }, + { + code: unIndent` + fs.readdirSync(path.join(__dirname, '../rules')).forEach(name => { + files[name] = foo; + }); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` + (function(x, y){ + function foo(x) { + return x + 1; + } + })(1, 2); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + }()); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + !function(){ + function foo(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + !function(){ + \t\t\tfunction foo(x) { + \t\t\t\treturn x + 1; + \t\t\t} + }(); + `, + options: ['tab', { outerIIFEBody: 3 }], + }, + { + code: unIndent` + var out = function(){ + function fooVar(x) { + return x + 1; + } + }; + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + ns = function(){ + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = (function(){ + function fooVar(x) { + return x + 1; + } + }(x)); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + var ns = (function(){ + function fooVar(x) { + return x + 1; + } + }(x)); + `, + options: [4, { outerIIFEBody: 2 }], + }, + { + code: unIndent` + var obj = { + foo: function() { + return true; + } + }; + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + while ( + function() { + return true; + }()) { + + x = x + 1; + }; + `, + options: [2, { outerIIFEBody: 20 }], + }, + { + code: unIndent` + (() => { + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + function foo() { + } + `, + options: ['tab', { outerIIFEBody: 0 }], + }, + { + code: unIndent` + ;(() => { + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: unIndent` + if(data) { + console.log('hi'); + } + `, + options: [2, { outerIIFEBody: 0 }], + }, + { + code: 'Buffer.length', + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .indexOf('a') + .toString() + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer. + length + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + \t.foo + \t.bar + `, + options: ['tab', { MemberExpression: 1 }], + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + options: [2, { MemberExpression: 2 }], + }, + unIndent` + ( + foo + .bar + ) + `, + unIndent` + ( + ( + foo + .bar + ) + ) + `, + unIndent` + ( + foo + ) + .bar + `, + unIndent` + ( + ( + foo + ) + .bar + ) + `, + unIndent` + ( + ( + foo + ) + [ + ( + bar + ) + ] + ) + `, + unIndent` + ( + foo[bar] + ) + .baz + `, + unIndent` + ( + (foo.bar) + ) + .baz + `, + { + code: unIndent` + MemberExpression + .can + .be + .turned + .off(); + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo = bar.baz() + .bip(); + `, + options: [4, { MemberExpression: 1 }], + }, + unIndent` + function foo() { + new + .target + } + `, + unIndent` + function foo() { + new. + target + } + `, + { + code: unIndent` + if (foo) { + bar(); + } else if (baz) { + foobar(); + } else if (qux) { + qux(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + }, + { + code: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + }, + { + code: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 1 } }], + }, + { + code: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + options: [2, { FunctionDeclaration: { body: 3 } }], + }, + { + code: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 2 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + }, + { + code: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + options: [4, { FunctionExpression: { parameters: 'first', body: 1 } }], + }, + { + code: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 'first', body: 3 } }], + }, + { + code: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + options: [ + 2, + { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }, + ], + }, + { + code: unIndent` + function foo() { + bar(); + \tbaz(); + \t \t\t\t \t\t\t \t \tqux(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1 } }], + }, + { + code: unIndent` + function foo() { + bar(); + \t\t} + `, + options: [2], + }, + { + code: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + }, + { + code: unIndent` + (( + foo + )) + `, + options: [4], + }, + + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` + foo + ? bar + : baz + `, + options: [2], + }, + { + code: unIndent` + foo = (bar ? + baz : + qux + ); + `, + options: [2], + }, + unIndent` + [ + foo ? + bar : + baz, + qux + ]; + `, + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/3845, https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` + foo(); + // Line + /* multiline + Line */ + bar(); + // trailing comment + `, + options: [2], + }, + { + code: unIndent` + switch (foo) { + case bar: + baz(); + // call the baz function + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + code: unIndent` + switch (foo) { + case bar: + baz(); + // no default + } + `, + options: [2, { SwitchCase: 1 }], + }, + unIndent` + [ + // no elements + ] + `, + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + options: [2], + }, + { + code: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + options: [2], + }, + { + code: unIndent` + const { + a + } + = + { + a: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const { + a + } = { + a: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const + { + a + } = { + a: 1 + }; + `, + options: [2], + }, + { + code: unIndent` + const + foo = { + bar: 1 + } + `, + options: [2], + }, + { + code: unIndent` + const [ + a + ] = [ + 1 + ] + `, + options: [2], + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` + var folder = filePath + .foo() + .bar; + `, + options: [2, { MemberExpression: 2 }], + }, + { + code: unIndent` + for (const foo of bar) + baz(); + `, + options: [2], + }, + { + code: unIndent` + var x = () => + 5; + `, + options: [2], + }, + unIndent` + ( + foo + )( + bar + ) + `, + unIndent` + (() => + foo + )( + bar + ) + `, + unIndent` + (() => { + foo(); + })( + bar + ) + `, + { + // Don't lint the indentation of the first token after a : + code: unIndent` + ({code: + "foo.bar();"}) + `, + options: [2], + }, + { + // Don't lint the indentation of the first token after a : + code: unIndent` + ({code: + "foo.bar();"}) + `, + options: [2], + }, + unIndent` + ({ + foo: + bar + }) + `, + unIndent` + ({ + [foo]: + bar + }) + `, + { + // Comments in switch cases + code: unIndent` + switch (foo) { + // comment + case study: + // comment + bar(); + case closed: + /* multiline comment + */ + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + // Comments in switch cases + code: unIndent` + switch (foo) { + // comment + case study: + // the comment can also be here + case closed: + } + `, + options: [2, { SwitchCase: 1 }], + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && ( + bar + ) + `, + options: [4], + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && (( + bar + )) + `, + options: [4], + }, + { + code: unIndent` + foo && + ( + bar + ) + `, + options: [4], + }, + unIndent` + foo && + !bar( + ) + `, + unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + { + code: unIndent` + foo = + bar; + `, + options: [4], + }, + { + code: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + options: [2, { FunctionExpression: { parameters: 3 } }], + }, + unIndent` + function foo() { + return (bar === 1 || bar === 2 && + (/Function/.test(grandparent.type))) && + directives(parent).indexOf(node) >= 0; + } + `, + { + code: unIndent` + function foo() { + return (foo === bar || ( + baz === qux && ( + foo === foo || + bar === bar || + baz === baz + ) + )) + } + `, + options: [4], + }, + unIndent` + if ( + foo === 1 || + bar === 1 || + // comment + (baz === 1 && qux === 1) + ) {} + `, + { + code: unIndent` + foo = + (bar + baz); + `, + options: [2], + }, + { + code: unIndent` + function foo() { + return (bar === 1 || bar === 2) && + (z === 3 || z === 4); + } + `, + options: [2], + }, + { + code: unIndent` + /* comment */ if (foo) { + bar(); + } + `, + options: [2], + }, + { + // Comments at the end of if blocks that have `else` blocks can either refer to the lines above or below them + code: unIndent` + if (foo) { + bar(); + // Otherwise, if foo is false, do baz. + // baz is very important. + } else { + baz(); + } + `, + options: [2], + }, + { + code: unIndent` + function foo() { + return ((bar === 1 || bar === 2) && + (z === 3 || z === 4)); + } + `, + options: [2], + }, + { + code: unIndent` + foo( + bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` + foo( + \tbar, + \tbaz, + \tqux + ); + `, + options: ['tab', { CallExpression: { arguments: 1 } }], + }, + { + code: unIndent` + foo(bar, + baz, + qux); + `, + options: [4, { CallExpression: { arguments: 2 } }], + }, + { + code: unIndent` + foo( + bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 0 } }], + }, + { + code: unIndent` + foo(bar, + baz, + qux + ); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo(bar, baz, + qux, barbaz, + barqux, bazqux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + options: [2, { CallExpression: { arguments: 4 } }], + }, + unIndent` + foo( + (bar) + ); + `, + { + code: unIndent` + foo( + (bar) + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + }, + + // https://github.com/eslint/eslint/issues/7484 + { + code: unIndent` + var foo = function() { + return bar( + [{ + }].concat(baz) + ); + }; + `, + options: [2], + }, + + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` + return ( + foo + ); + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + { + code: unIndent` + return ( + foo + ) + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + }, + unIndent` + var foo = [ + bar, + baz + ] + `, + unIndent` + var foo = [bar, + baz, + qux + ] + `, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 0 }], + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 8 }], + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [bar, + baz, qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + baz: 4 } + ] + `, + options: [4, { ArrayExpression: 2, ObjectExpression: 'first' }], + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + options: [2, { ObjectExpression: 0 }], + }, + { + code: unIndent` + var foo = { foo: 1, bar: 2, + baz: 3 } + `, + options: [2, { ObjectExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ + { + foo: 1 + } + ] + `, + options: [4, { ArrayExpression: 2 }], + }, + { + code: unIndent` + function foo() { + [ + foo + ] + } + `, + options: [2, { ArrayExpression: 4 }], + }, + { + code: '[\n]', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: '[\n]', + options: [2, { ArrayExpression: 1 }], + }, + { + code: '{\n}', + options: [2, { ObjectExpression: 'first' }], + }, + { + code: '{\n}', + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` + var foo = [ + [ + 1 + ] + ] + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = [ 1, + [ + 2 + ] + ]; + `, + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + var foo = bar(1, + [ 2, + 3 + ] + ); + `, + options: [ + 4, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + }, + { + code: unIndent` + var foo = + [ + ]() + `, + options: [ + 4, + { CallExpression: { arguments: 'first' }, ArrayExpression: 'first' }, + ], + }, + + // https://github.com/eslint/eslint/issues/7732 + { + code: unIndent` + const lambda = foo => { + Object.assign({}, + filterName, + { + display + } + ); + } + `, + options: [2, { ObjectExpression: 1 }], + }, + { + code: unIndent` + const lambda = foo => { + Object.assign({}, + filterName, + { + display + } + ); + } + `, + options: [2, { ObjectExpression: 'first' }], + }, + + // https://github.com/eslint/eslint/issues/7733 + { + code: unIndent` + var foo = function() { + \twindow.foo('foo', + \t\t{ + \t\t\tfoo: 'bar', + \t\t\tbar: { + \t\t\t\tfoo: 'bar' + \t\t\t} + \t\t} + \t); + } + `, + options: ['tab'], + }, + { + code: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + options: [ + 2, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + }, + { + code: unIndent` + if (foo) + bar(); + // Otherwise, if foo is false, do baz. + // baz is very important. + else { + baz(); + } + `, + options: [2], + }, + { + code: unIndent` + if ( + foo && bar || + baz && qux // This line is ignored because BinaryExpressions are not checked. + ) { + qux(); + } + `, + options: [4], + }, + unIndent` + [ + ] || [ + ] + `, + unIndent` + ( + [ + ] || [ + ] + ) + `, + unIndent` + 1 + + ( + 1 + ) + `, + unIndent` + ( + foo && ( + bar || + baz + ) + ) + `, + unIndent` + foo + || ( + bar + ) + `, + unIndent` + foo + || ( + bar + ) + `, + { + code: unIndent` + var foo = + 1; + `, + options: [4, { VariableDeclarator: 2 }], + }, + { + code: unIndent` + var foo = 1, + bar = + 2; + `, + options: [4], + }, + { + code: unIndent` + switch (foo) { + case bar: + { + baz(); + } + } + `, + options: [2, { SwitchCase: 1 }], + }, + + // Template curlies + { + code: unIndent` + \`foo\${ + bar}\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + options: [2], + }, + { + code: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + options: [2], + }, + unIndent` + foo(\` + bar + \`, { + baz: 1 + }); + `, + unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + unIndent` + JSON + .stringify( + { + ok: true + } + ); + `, + + // Don't check AssignmentExpression assignments + unIndent` + foo = + bar = + baz; + `, + unIndent` + foo = + bar = + baz; + `, + unIndent` + function foo() { + const template = \`this indentation is not checked + because it's part of a template literal.\`; + } + `, + unIndent` + function foo() { + const template = \`the indentation of a \${ + node.type + } node is checked.\`; + } + `, + { + // https://github.com/eslint/eslint/issues/7320 + code: unIndent` + JSON + .stringify( + { + test: 'test' + } + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + }, + unIndent` + [ + foo, + // comment + // another comment + bar + ] + `, + unIndent` + if (foo) { + /* comment */ bar(); + } + `, + unIndent` + function foo() { + return ( + 1 + ); + } + `, + unIndent` + function foo() { + return ( + 1 + ) + } + `, + unIndent` + if ( + foo && + !( + bar + ) + ) {} + `, + { + // https://github.com/eslint/eslint/issues/6007 + code: unIndent` + var abc = [ + ( + '' + ), + def, + ] + `, + options: [2], + }, + { + code: unIndent` + var abc = [ + ( + '' + ), + ( + 'bar' + ) + ] + `, + options: [2], + }, + unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + options: [4, { MemberExpression: 1 }], + }, + + // https://github.com/eslint/eslint/issues/7242 + unIndent` + var x = [ + [1], + [2] + ] + `, + unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + unIndent` + foo( + ) + `, + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + }, + 'new Foo', + 'new (Foo)', + unIndent` + if (Foo) { + new Foo + } + `, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + var a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return ( + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + ) + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return foo + ? bar + : baz + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function wrap() { + return ( + foo + ? bar + : baz + ) + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + foo + ? bar + : baz + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo(foo + ? bar + : baz + ) + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + }, + { + code: '[,]', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: '[,]', + options: [2, { ArrayExpression: 'off' }], + }, + { + code: unIndent` + [ + , + foo + ] + `, + options: [4, { ArrayExpression: 'first' }], + }, + { + code: '[sparse, , array];', + options: [2, { ArrayExpression: 'first' }], + }, + { + code: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + }, + { + code: unIndent` + ( + { + foo: 1, + baz: 2 + } + ); + `, + options: [2, { ObjectExpression: 'first' }], + }, + { + code: unIndent` + foo(() => { + bar; + }, () => { + baz; + }) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + }, + { + code: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + options: [2, { ArrayExpression: 'first', MemberExpression: 1 }], + }, + unIndent` + foo = bar[ + baz + ]; + `, + { + code: unIndent` + foo[ + bar + ]; + `, + options: [4, { MemberExpression: 1 }], + }, + { + code: unIndent` + foo[ + ( + bar + ) + ]; + `, + options: [4, { MemberExpression: 1 }], + }, + unIndent` + if (foo) + bar; + else if (baz) + qux; + `, + unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + unIndent` + if (foo) + ; + `, + 'x => {}', + { + code: unIndent` + import {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + }, + { + code: "import 'foo'", + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 1 }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { + foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 1 }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { apple as a, + banana as b } from 'fruits'; + import { cat } from 'animals'; + `, + options: [4, { ImportDeclaration: 'first' }], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + import { declaration, + can, + be, + turned } from 'off'; + `, + options: [4, { ImportDeclaration: 'off' }], + parserOptions: { sourceType: 'module' }, + }, + + // https://github.com/eslint/eslint/issues/8455 + unIndent` + ( + a + ) => b => { + c + } + `, + unIndent` + ( + a + ) => b => c => d => { + e + } + `, + unIndent` + ( + a + ) => + ( + b + ) => { + c + } + `, + unIndent` + if ( + foo + ) bar( + baz + ); + `, + unIndent` + if (foo) + { + bar(); + } + `, + unIndent` + function foo(bar) + { + baz(); + } + `, + unIndent` + () => + ({}) + `, + unIndent` + () => + (({})) + `, + unIndent` + ( + () => + ({}) + ) + `, + unIndent` + var x = function foop(bar) + { + baz(); + } + `, + unIndent` + var x = (bar) => + { + baz(); + } + `, + unIndent` + class Foo + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + `, + unIndent` + class Foo + extends Bar + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + `, + unIndent` + ( + class Foo + { + constructor() + { + foo(); + } + + bar() + { + baz(); + } + } + ) + `, + { + code: unIndent` + switch (foo) + { + case 1: + bar(); + } + `, + options: [4, { SwitchCase: 1 }], + }, + unIndent` + foo + .bar(function() { + baz + }) + `, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 2 }], + }, + unIndent` + foo + [bar](function() { + baz + }) + `, + unIndent` + foo. + bar. + baz + `, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo + .bar(function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo + [bar](function() { + baz + }) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo. + bar. + baz + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo = bar( + ).baz( + ) + `, + options: [4, { MemberExpression: 'off' }], + }, + { + code: unIndent` + foo[ + bar ? baz : + qux + ] + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + function foo() { + return foo ? bar : + baz + } + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + throw foo ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + }, + { + code: unIndent` + foo( + bar + ) ? baz : + qux + `, + options: [4, { flatTernaryExpressions: true }], + }, + unIndent` + foo + [ + bar + ] + .baz(function() { + quz(); + }) + `, + unIndent` + [ + foo + ][ + "map"](function() { + qux(); + }) + `, + unIndent` + ( + a.b(function() { + c; + }) + ) + `, + unIndent` + ( + foo + ).bar(function() { + baz(); + }) + `, + unIndent` + new Foo( + bar + .baz + .qux + ) + `, + unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + unIndent` + const foo = a.b(), + longName = + baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName = + baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName + = baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName + = baz( + 'bar', + 'bar' + ); + `, + unIndent` + const foo = a.b(), + longName = + ('fff'); + `, + unIndent` + const foo = a.b(), + longName = + ('fff'); + `, + unIndent` + const foo = a.b(), + longName + = ('fff'); + + `, + unIndent` + const foo = a.b(), + longName + = ('fff'); + + `, + unIndent` + const foo = a.b(), + longName = + ( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName = + ( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + + unIndent` + foo(\`foo + \`, { + ok: true + }, + { + ok: false + }) + `, + unIndent` + foo(tag\`foo + \`, { + ok: true + }, + { + ok: false + } + ) + `, + + // https://github.com/eslint/eslint/issues/8815 + unIndent` + async function test() { + const { + foo, + bar, + } = await doSomethingAsync( + 1, + 2, + 3, + ); + } + `, + unIndent` + function* test() { + const { + foo, + bar, + } = yield doSomethingAsync( + 1, + 2, + 3, + ); + } + `, + unIndent` + ({ + a: b + } = +foo( + bar + )); + `, + unIndent` + const { + foo, + bar, + } = typeof foo( + 1, + 2, + 3, + ); + `, + unIndent` + const { + foo, + bar, + } = +( + foo + ); + `, + + //---------------------------------------------------------------------- + // JSX tests + // https://github.com/eslint/eslint/issues/8425 + // Some of the following tests are adapted from the the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- + + ';', + unIndent` + ; + `, + 'var foo = ;', + unIndent` + var foo = ; + `, + unIndent` + var foo = (); + `, + unIndent` + var foo = ( + + ); + `, + unIndent` + < + Foo + a="b" + c="d" + />; + `, + unIndent` + ; + `, + unIndent` + < + Foo + a="b" + c="d"/>; + `, + 'bar;', + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + < + a + href="foo"> + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + + bar + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = < + a + href="bar"> + baz + ; + `, + unIndent` + var foo = + baz + ; + `, + unIndent` + var foo = + baz + + `, + unIndent` + var foo = ( + baz + ); + `, + unIndent` + var foo = ( + baz + ); + `, + unIndent` + var foo = ( + + baz + + ); + `, + unIndent` + var foo = ( + + baz + + ); + `, + 'var foo = baz;', + unIndent` + + { + } + + `, + unIndent` + + { + foo + } + + `, + unIndent` + function foo() { + return ( + + { + b.forEach(() => { + // comment + a = c + .d() + .e(); + }) + } + + ); + } + `, + '', + unIndent` + + + `, + { + code: unIndent` + + + + `, + options: [2], + }, + { + code: unIndent` + + + + `, + options: [0], + }, + { + code: unIndent` + + \t + + `, + options: ['tab'], + }, + { + code: unIndent` + function App() { + return + + ; + } + `, + options: [2], + }, + { + code: unIndent` + function App() { + return ( + + ); + } + `, + options: [2], + }, + { + code: unIndent` + function App() { + return ( + + + + ); + } + `, + options: [2], + }, + { + code: unIndent` + it( + ( +
+ +
+ ) + ) + `, + options: [2], + }, + { + code: unIndent` + it( + (
+ + + +
) + ) + `, + options: [2], + }, + { + code: unIndent` + ( +
+ +
+ ) + `, + options: [2], + }, + { + code: unIndent` + { + head.title && +

+ {head.title} +

+ } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && +

+ {head.title} +

+ } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && ( +

+ {head.title} +

) + } + `, + options: [2], + }, + { + code: unIndent` + { + head.title && ( +

+ {head.title} +

+ ) + } + `, + options: [2], + }, + { + code: unIndent` + [ +
, +
+ ] + `, + options: [2], + }, + unIndent` +
+ { + [ + , + + ] + } +
+ `, + unIndent` +
+ {foo && + [ + , + + ] + } +
+ `, + unIndent` +
+ bar
+ bar + bar {foo} + bar
+
+ `, + unIndent` + foo ? + : + + `, + unIndent` + foo ? + + : + `, + unIndent` + foo ? + + : + + `, + unIndent` +
+ {!foo ? + + : + + } +
+ `, + { + code: unIndent` + + {condition ? + : + + } + + `, + options: [2], + }, + { + code: unIndent` + + {condition ? + : + + } + + `, + options: [2], + }, + { + code: unIndent` + function foo() { + + {condition ? + : + + } + + } + `, + options: [2], + }, + unIndent` + + `, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: [0], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + unIndent` + + `, + unIndent` + + `, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: [2], + }, + { + code: unIndent` + var x = function() { + return + } + `, + options: [2], + }, + { + code: unIndent` + var x = + `, + options: [2], + }, + { + code: unIndent` + + + + `, + options: [2], + }, + { + code: unIndent` + + {baz && } + + `, + options: [2], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + + `, + options: ['tab'], + }, + { + code: unIndent` + var x = + `, + options: ['tab'], + }, + unIndent` + + `, + unIndent` +
+ unrelated{ + foo + } +
+ `, + unIndent` +
unrelated{ + foo + } +
+ `, + unIndent` + < + foo + .bar + .baz + > + foo + + `, + unIndent` + < + input + type= + "number" + /> + `, + unIndent` + < + input + type= + {'number'} + /> + `, + unIndent` + < + input + type + ="number" + /> + `, + unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + unIndent` +
+ { + /* foo */ + } +
+ `, + + // https://github.com/eslint/eslint/issues/8832 + unIndent` +
+ { + ( + 1 + ) + } +
+ `, + unIndent` + function A() { + return ( +
+ { + b && ( +
+
+ ) + } +
+ ); + } + `, + unIndent` +
foo +
bar
+
+ `, + unIndent` + Foo bar  + baz qux. + + `, + { + code: unIndent` + a(b + , c + ) + `, + options: [2, { CallExpression: { arguments: 'off' } }], + }, + { + code: unIndent` + a( + new B({ + c, + }) + ); + `, + options: [2, { CallExpression: { arguments: 'off' } }], + }, + { + code: unIndent` + foo + ? bar + : baz + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [4, { ignoredNodes: ['ClassBody'] }], + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [ + 4, + { ignoredNodes: ['ClassBody', AST_NODE_TYPES.BlockStatement] }, + ], + }, + { + code: unIndent` + foo({ + bar: 1 + }, + { + baz: 2 + }, + { + qux: 3 + }) + `, + options: [4, { ignoredNodes: ['CallExpression > ObjectExpression'] }], + }, + { + code: unIndent` + foo + .bar + `, + options: [4, { ignoredNodes: ['MemberExpression'] }], + }, + { + code: unIndent` + $(function() { + + foo(); + bar(); + + }); + `, + options: [ + 4, + { + ignoredNodes: [ + "Program > ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement", + ], + }, + ], + }, + { + code: unIndent` + + `, + options: [4, { ignoredNodes: ['JSXOpeningElement'] }], + }, + { + code: unIndent` + foo && + + + `, + options: [ + 4, + { ignoredNodes: ['JSXElement', AST_NODE_TYPES.JSXOpeningElement] }, + ], + }, + { + code: unIndent` + (function($) { + $(function() { + foo; + }); + }()) + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement', + ], + }, + ], + }, + { + code: unIndent` + const value = ( + condition ? + valueIfTrue : + valueIfFalse + ); + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + var a = 0, b = 0, c = 0; + export default foo( + a, + b, { + c + } + ) + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExportDefaultDeclaration > CallExpression > ObjectExpression', + ], + }, + ], + parserOptions: { sourceType: 'module' }, + }, + { + code: unIndent` + foobar = baz + ? qux + : boop + `, + options: [4, { ignoredNodes: ['ConditionalExpression'] }], + }, + { + code: unIndent` + \` + SELECT + \${ + foo + } FROM THE_DATABASE + \` + `, + options: [4, { ignoredNodes: ['TemplateLiteral'] }], + }, + { + code: unIndent` + + Text + + `, + options: [4, { ignoredNodes: ['JSXOpeningElement'] }], + }, + { + code: unIndent` + { + \tvar x = 1, + \t y = 2; + } + `, + options: ['tab'], + }, + { + code: unIndent` + var x = 1, + y = 2; + var z; + `, + options: ['tab', { ignoredNodes: ['VariableDeclarator'] }], + }, + { + code: unIndent` + [ + foo(), + bar + ] + `, + options: [ + 'tab', + { ArrayExpression: 'first', ignoredNodes: ['CallExpression'] }, + ], + }, + { + code: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + options: [4, { ignoreComments: true }], + }, + { + code: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + options: [4, { ignoreComments: true }], + }, + unIndent` + const obj = { + foo () { + return condition ? // comment + 1 : + 2 + } + } + `, + + //---------------------------------------------------------------------- + // Comment alignment tests + //---------------------------------------------------------------------- + unIndent` + if (foo) { + // Comment can align with code immediately above even if "incorrect" alignment + doSomething(); + } + `, + unIndent` + if (foo) { + doSomething(); + // Comment can align with code immediately below even if "incorrect" alignment + } + `, + unIndent` + if (foo) { + // Comment can be in correct alignment even if not aligned with code above/below + } + `, + unIndent` + if (foo) { + + // Comment can be in correct alignment even if gaps between (and not aligned with) code above/below + + } + `, + unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + unIndent` + [{ + foo + }, + + // Comment between nodes + + { // comment + bar + }]; + `, + ], + + invalid: [ + { + code: unIndent` + var a = b; + if (a) { + b(); + } + `, + output: unIndent` + var a = b; + if (a) { + b(); + } + `, + options: [2], + errors: expectedErrors([[3, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + output: unIndent` + require('http').request({hostname: 'localhost', + port: 80}, function(res) { + res.end(); + }); + `, + options: [2], + errors: expectedErrors([ + [2, 2, 18, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if (array.some(function(){ + return true; + })) { + a++; // -> + b++; + c++; // <- + } + `, + output: unIndent` + if (array.some(function(){ + return true; + })) { + a++; // -> + b++; + c++; // <- + } + `, + options: [2], + errors: expectedErrors([ + [4, 2, 0, AST_TOKEN_TYPES.Identifier], + [6, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (a){ + \tb=c; + \t\tc=d; + e=f; + } + `, + output: unIndent` + if (a){ + \tb=c; + \tc=d; + \te=f; + } + `, + options: ['tab'], + errors: expectedErrors('tab', [ + [3, 1, 2, AST_TOKEN_TYPES.Identifier], + [4, 1, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (a){ + b=c; + c=d; + e=f; + } + `, + output: unIndent` + if (a){ + b=c; + c=d; + e=f; + } + `, + options: [4], + errors: expectedErrors([ + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + [4, 4, 1, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: fixture, + output: fixedFixture, + options: [ + 2, + { + SwitchCase: 1, + MemberExpression: 1, + CallExpression: { arguments: 'off' }, + }, + ], + errors: expectedErrors([ + [5, 2, 4, AST_TOKEN_TYPES.Keyword], + [6, 2, 0, AST_TOKEN_TYPES.Line], + [10, 4, 6, AST_TOKEN_TYPES.Punctuator], + [11, 2, 4, AST_TOKEN_TYPES.Punctuator], + + [15, 4, 2, AST_TOKEN_TYPES.Identifier], + [16, 2, 4, AST_TOKEN_TYPES.Punctuator], + [23, 2, 4, AST_TOKEN_TYPES.Punctuator], + [29, 2, 4, AST_TOKEN_TYPES.Keyword], + [30, 4, 6, AST_TOKEN_TYPES.Identifier], + [36, 4, 6, AST_TOKEN_TYPES.Identifier], + [38, 2, 4, AST_TOKEN_TYPES.Punctuator], + [39, 4, 2, AST_TOKEN_TYPES.Identifier], + [40, 2, 0, AST_TOKEN_TYPES.Punctuator], + [54, 2, 4, AST_TOKEN_TYPES.Punctuator], + [114, 4, 2, AST_TOKEN_TYPES.Keyword], + [120, 4, 6, AST_TOKEN_TYPES.Keyword], + [124, 4, 2, AST_TOKEN_TYPES.Keyword], + [134, 4, 6, AST_TOKEN_TYPES.Keyword], + [138, 2, 3, AST_TOKEN_TYPES.Punctuator], + [139, 2, 3, AST_TOKEN_TYPES.Punctuator], + [143, 4, 0, AST_TOKEN_TYPES.Identifier], + [144, 6, 2, AST_TOKEN_TYPES.Punctuator], + [145, 6, 2, AST_TOKEN_TYPES.Punctuator], + [151, 4, 6, AST_TOKEN_TYPES.Identifier], + [152, 6, 8, AST_TOKEN_TYPES.Punctuator], + [153, 6, 8, AST_TOKEN_TYPES.Punctuator], + [159, 4, 2, AST_TOKEN_TYPES.Identifier], + [161, 4, 6, AST_TOKEN_TYPES.Identifier], + [175, 2, 0, AST_TOKEN_TYPES.Identifier], + [177, 2, 4, AST_TOKEN_TYPES.Identifier], + [189, 2, 0, AST_TOKEN_TYPES.Keyword], + [192, 6, 18, AST_TOKEN_TYPES.Identifier], + [193, 6, 4, AST_TOKEN_TYPES.Identifier], + [195, 6, 8, AST_TOKEN_TYPES.Identifier], + [228, 5, 4, AST_TOKEN_TYPES.Identifier], + [231, 3, 2, AST_TOKEN_TYPES.Punctuator], + [245, 0, 2, AST_TOKEN_TYPES.Punctuator], + [248, 0, 2, AST_TOKEN_TYPES.Punctuator], + [304, 4, 6, AST_TOKEN_TYPES.Identifier], + [306, 4, 8, AST_TOKEN_TYPES.Identifier], + [307, 2, 4, AST_TOKEN_TYPES.Punctuator], + [308, 2, 4, AST_TOKEN_TYPES.Identifier], + [311, 4, 6, AST_TOKEN_TYPES.Identifier], + [312, 4, 6, AST_TOKEN_TYPES.Identifier], + [313, 4, 6, AST_TOKEN_TYPES.Identifier], + [314, 2, 4, AST_TOKEN_TYPES.Punctuator], + [315, 2, 4, AST_TOKEN_TYPES.Identifier], + [318, 4, 6, AST_TOKEN_TYPES.Identifier], + [319, 4, 6, AST_TOKEN_TYPES.Identifier], + [320, 4, 6, AST_TOKEN_TYPES.Identifier], + [321, 2, 4, AST_TOKEN_TYPES.Punctuator], + [322, 2, 4, AST_TOKEN_TYPES.Identifier], + [326, 2, 1, AST_TOKEN_TYPES.Numeric], + [327, 2, 1, AST_TOKEN_TYPES.Numeric], + [328, 2, 1, AST_TOKEN_TYPES.Numeric], + [329, 2, 1, AST_TOKEN_TYPES.Numeric], + [330, 2, 1, AST_TOKEN_TYPES.Numeric], + [331, 2, 1, AST_TOKEN_TYPES.Numeric], + [332, 2, 1, AST_TOKEN_TYPES.Numeric], + [333, 2, 1, AST_TOKEN_TYPES.Numeric], + [334, 2, 1, AST_TOKEN_TYPES.Numeric], + [335, 2, 1, AST_TOKEN_TYPES.Numeric], + [340, 2, 4, AST_TOKEN_TYPES.Identifier], + [341, 2, 0, AST_TOKEN_TYPES.Identifier], + [344, 2, 4, AST_TOKEN_TYPES.Identifier], + [345, 2, 0, AST_TOKEN_TYPES.Identifier], + [348, 2, 4, AST_TOKEN_TYPES.Identifier], + [349, 2, 0, AST_TOKEN_TYPES.Identifier], + [355, 2, 0, AST_TOKEN_TYPES.Identifier], + [357, 2, 4, AST_TOKEN_TYPES.Identifier], + [361, 4, 6, AST_TOKEN_TYPES.Identifier], + [362, 2, 4, AST_TOKEN_TYPES.Punctuator], + [363, 2, 4, AST_TOKEN_TYPES.Identifier], + [368, 2, 0, AST_TOKEN_TYPES.Keyword], + [370, 2, 4, AST_TOKEN_TYPES.Keyword], + [374, 4, 6, AST_TOKEN_TYPES.Keyword], + [376, 4, 2, AST_TOKEN_TYPES.Keyword], + [383, 2, 0, AST_TOKEN_TYPES.Identifier], + [385, 2, 4, AST_TOKEN_TYPES.Identifier], + [390, 2, 0, AST_TOKEN_TYPES.Identifier], + [392, 2, 4, AST_TOKEN_TYPES.Identifier], + [409, 2, 0, AST_TOKEN_TYPES.Identifier], + [410, 2, 4, AST_TOKEN_TYPES.Identifier], + [416, 2, 0, AST_TOKEN_TYPES.Identifier], + [417, 2, 4, AST_TOKEN_TYPES.Identifier], + [418, 0, 4, AST_TOKEN_TYPES.Punctuator], + [422, 2, 4, AST_TOKEN_TYPES.Identifier], + [423, 2, 0, AST_TOKEN_TYPES.Identifier], + [427, 2, 6, AST_TOKEN_TYPES.Identifier], + [428, 2, 8, AST_TOKEN_TYPES.Identifier], + [429, 2, 4, AST_TOKEN_TYPES.Identifier], + [430, 0, 4, AST_TOKEN_TYPES.Punctuator], + [433, 2, 4, AST_TOKEN_TYPES.Identifier], + [434, 0, 4, AST_TOKEN_TYPES.Punctuator], + [437, 2, 0, AST_TOKEN_TYPES.Identifier], + [438, 0, 4, AST_TOKEN_TYPES.Punctuator], + [442, 2, 4, AST_TOKEN_TYPES.Identifier], + [443, 2, 4, AST_TOKEN_TYPES.Identifier], + [444, 0, 2, AST_TOKEN_TYPES.Punctuator], + [451, 2, 0, AST_TOKEN_TYPES.Identifier], + [453, 2, 4, AST_TOKEN_TYPES.Identifier], + [499, 6, 8, AST_TOKEN_TYPES.Punctuator], + [500, 8, 6, AST_TOKEN_TYPES.Identifier], + [504, 4, 6, AST_TOKEN_TYPES.Punctuator], + [505, 6, 8, AST_TOKEN_TYPES.Identifier], + [506, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Keyword], + [7, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var x = 0 && + { + a: 1, + b: 2 + }; + `, + options: [4], + errors: expectedErrors([ + [3, 8, 7, AST_TOKEN_TYPES.Identifier], + [4, 8, 10, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + a(); + break; + default: + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([9, 8, 4, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + break; + } + switch(value){ + case "1": + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + output: unIndent` + switch(value){ + case "1": + case "2": + a(); + break; + default: + break; + } + switch(value){ + case "1": + break; + case "2": + a(); + break; + default: + a(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [11, 8, 4, AST_TOKEN_TYPES.Keyword], + [14, 8, 4, AST_TOKEN_TYPES.Keyword], + [17, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + output: unIndent` + switch(value){ + case "1": + a(); + break; + case "2": + break; + default: + break; + } + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 8, 4, AST_TOKEN_TYPES.Keyword], + [5, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 8, 4, AST_TOKEN_TYPES.Keyword], + [7, 4, 0, AST_TOKEN_TYPES.Keyword], + [8, 8, 4, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + output: unIndent` + var obj = {foo: 1, bar: 2}; + with (obj) { + console.log(foo + bar); + } + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + output: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + options: [4, { SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 8, 0, AST_TOKEN_TYPES.Identifier], + [4, 8, 0, AST_TOKEN_TYPES.Keyword], + [5, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 8, 0, AST_TOKEN_TYPES.Identifier], + [7, 8, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var foo = function(){ + foo + .bar + } + `, + output: unIndent` + var foo = function(){ + foo + .bar + } + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 8, 10, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ( + foo + .bar + ) + `, + output: unIndent` + ( + foo + .bar + ) + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var foo = function(){ + foo + .bar + } + `, + output: unIndent` + var foo = function(){ + foo + .bar + } + `, + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var foo = () => { + foo + .bar + } + `, + output: unIndent` + var foo = () => { + foo + .bar + } + `, + options: [4, { MemberExpression: 2 }], + errors: expectedErrors([3, 12, 13, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + TestClass.prototype.method = function () { + return Promise.resolve(3) + .then(function (x) { + return x; + }); + }; + `, + output: unIndent` + TestClass.prototype.method = function () { + return Promise.resolve(3) + .then(function (x) { + return x; + }); + }; + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([3, 4, 6, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + while (a) + b(); + `, + output: unIndent` + while (a) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + lmn = [{ + a: 1 + }, + { + b: 2 + }, + { + x: 2 + }]; + `, + output: unIndent` + lmn = [{ + a: 1 + }, + { + b: 2 + }, + { + x: 2 + }]; + `, + errors: expectedErrors([ + [2, 4, 8, AST_TOKEN_TYPES.Identifier], + [3, 0, 4, AST_TOKEN_TYPES.Punctuator], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + [5, 4, 8, AST_TOKEN_TYPES.Identifier], + [6, 0, 4, AST_TOKEN_TYPES.Punctuator], + [7, 0, 4, AST_TOKEN_TYPES.Punctuator], + [8, 4, 8, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + output: unIndent` + for (var foo = 1; + foo < 10; + foo++) {} + `, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + output: unIndent` + for ( + var foo = 1; + foo < 10; + foo++ + ) {} + `, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Keyword], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + for (;;) + b(); + `, + output: unIndent` + for (;;) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + for (a in x) + b(); + `, + output: unIndent` + for (a in x) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + do + b(); + while(true) + `, + output: unIndent` + do + b(); + while(true) + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + if(true) + b(); + `, + output: unIndent` + if(true) + b(); + `, + options: [4], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var test = { + a: 1, + b: 2 + }; + `, + output: unIndent` + var test = { + a: 1, + b: 2 + }; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 6, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var a = function() { + a++; + b++; + c++; + }, + b; + `, + output: unIndent` + var a = function() { + a++; + b++; + c++; + }, + b; + `, + options: [4], + errors: expectedErrors([ + [2, 8, 6, AST_TOKEN_TYPES.Identifier], + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 8, 10, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = 1, + b = 2, + c = 3; + `, + output: unIndent` + var a = 1, + b = 2, + c = 3; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + [a, b, + c].forEach((index) => { + index; + }); + `, + output: unIndent` + [a, b, + c].forEach((index) => { + index; + }); + `, + options: [4], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + output: unIndent` + [a, b, + c].forEach(function(index){ + return index; + }); + `, + options: [4], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + output: unIndent` + [a, b, c].forEach(function(index){ + return index; + }); + `, + options: [4], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + (foo) + .bar([ + baz + ]); + `, + output: unIndent` + (foo) + .bar([ + baz + ]); + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var x = ['a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = ['a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c', + 'd']; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c', + 'd']; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + [5, 4, 0, AST_TOKEN_TYPES.String], + ]), + }, + { + code: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + output: unIndent` + var x = [ + 'a', + 'b', + 'c' + ]; + `, + options: [4], + errors: expectedErrors([ + [2, 4, 9, AST_TOKEN_TYPES.String], + [3, 4, 9, AST_TOKEN_TYPES.String], + [4, 4, 9, AST_TOKEN_TYPES.String], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [[ + ], function( + foo + ) {} + ] + `, + output: unIndent` + [[ + ], function( + foo + ) {} + ] + `, + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + output: unIndent` + define([ + 'foo' + ], function( + bar + ) { + baz; + } + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + while (1 < 2) + console.log('foo') + console.log('bar') + `, + output: unIndent` + while (1 < 2) + console.log('foo') + console.log('bar') + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + output: unIndent` + function salutation () { + switch (1) { + case 0: return console.log('hi') + case 1: return console.log('hey') + } + } + `, + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[3, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + output: unIndent` + var geometry, box, face1, face2, colorT, colorB, sprite, padding, maxWidth, + height, rotate; + `, + options: [2, { SwitchCase: 1 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + output: unIndent` + switch (a) { + case '1': + b(); + break; + default: + c(); + break; + } + `, + options: [4, { SwitchCase: 2 }], + errors: expectedErrors([ + [2, 8, 0, AST_TOKEN_TYPES.Keyword], + [3, 12, 0, AST_TOKEN_TYPES.Identifier], + [4, 12, 0, AST_TOKEN_TYPES.Keyword], + [5, 8, 0, AST_TOKEN_TYPES.Keyword], + [6, 12, 0, AST_TOKEN_TYPES.Identifier], + [7, 12, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + var geometry, + rotate; + `, + output: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 1 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var geometry, + rotate; + `, + output: unIndent` + var geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var geometry, + \trotate; + `, + output: unIndent` + var geometry, + \t\trotate; + `, + options: ['tab', { VariableDeclarator: 2 }], + errors: expectedErrors('tab', [[2, 2, 1, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + let geometry, + rotate; + `, + output: unIndent` + let geometry, + rotate; + `, + options: [2, { VariableDeclarator: 2 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + output: unIndent` + let foo = 'foo', + bar = bar; + const a = 'a', + b = 'b'; + `, + options: [2, { VariableDeclarator: 'first' }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 6, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = 'foo', + bar = bar; + `, + output: unIndent` + var foo = 'foo', + bar = bar; + `, + options: [2, { VariableDeclarator: { var: 'first' } }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + output: unIndent` + if(true) + if (true) + if (true) + console.log(val); + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var a = { + a: 1, + b: 2 + } + `, + output: unIndent` + var a = { + a: 1, + b: 2 + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = [ + a, + b + ] + `, + output: unIndent` + var a = [ + a, + b + ] + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + let a = [ + a, + b + ] + `, + output: unIndent` + let a = [ + a, + b + ] + `, + options: [2, { VariableDeclarator: { let: 2 }, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + `, + output: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + `, + options: [4], + errors: expectedErrors([ + [2, 8, 6, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + const c = new Test({ + a: 1 + }), + d = 4; + `, + output: unIndent` + var a = new Test({ + a: 1 + }), + b = 4; + const c = new Test({ + a: 1 + }), + d = 4; + `, + options: [2, { VariableDeclarator: { var: 2 } }], + errors: expectedErrors([ + [6, 4, 6, AST_TOKEN_TYPES.Identifier], + [7, 2, 4, AST_TOKEN_TYPES.Punctuator], + [8, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var abc = 5, + c = 2, + xyz = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([6, 6, 7, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + output: unIndent` + var abc = + { + a: 1, + b: 2 + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([4, 7, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + output: unIndent` + var foo = { + bar: 1, + baz: { + qux: 2 + } + }, + bar = 1; + `, + options: [2], + errors: expectedErrors([ + [4, 6, 8, AST_TOKEN_TYPES.Identifier], + [5, 4, 6, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + output: unIndent` + var path = require('path') + , crypto = require('crypto') + ; + `, + options: [2], + errors: expectedErrors([[2, 2, 1, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + var a = 1 + ,b = 2 + ; + `, + output: unIndent` + var a = 1 + ,b = 2 + ; + `, + errors: expectedErrors([[2, 4, 3, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + output: unIndent` + class A{ + constructor(){} + a(){} + get b(){} + } + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([[2, 4, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + }; + `, + output: unIndent` + var A = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [4, { VariableDeclarator: 1, SwitchCase: 1 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + output: unIndent` + var a = 1, + B = class { + constructor(){} + a(){} + get b(){} + }; + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[3, 6, 4, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + { + if(a){ + foo(); + } + else{ + bar(); + } + } + `, + output: unIndent` + { + if(a){ + foo(); + } + else{ + bar(); + } + } + `, + options: [4], + errors: expectedErrors([[5, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + { + if(a){ + foo(); + } + else + bar(); + + } + `, + output: unIndent` + { + if(a){ + foo(); + } + else + bar(); + + } + `, + options: [4], + errors: expectedErrors([[5, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + { + if(a) + foo(); + else + bar(); + } + `, + output: unIndent` + { + if(a) + foo(); + else + bar(); + } + `, + options: [4], + errors: expectedErrors([[4, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + output: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Keyword], + [3, 2, 4, AST_TOKEN_TYPES.Keyword], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + output: unIndent` + (function(){ + function foo(x) { + return x + 1; + } + })(); + `, + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, AST_TOKEN_TYPES.Keyword], + [3, 12, 8, AST_TOKEN_TYPES.Keyword], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if(data) { + console.log('hi'); + } + `, + output: unIndent` + if(data) { + console.log('hi'); + } + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[2, 2, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(x); + `, + output: unIndent` + var ns = function(){ + function fooVar(x) { + return x + 1; + } + }(x); + `, + options: [4, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 8, 4, AST_TOKEN_TYPES.Keyword], + [3, 12, 8, AST_TOKEN_TYPES.Keyword], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var obj = { + foo: function() { + return true; + }() + }; + `, + output: unIndent` + var obj = { + foo: function() { + return true; + }() + }; + `, + options: [2, { outerIIFEBody: 0 }], + errors: expectedErrors([[3, 4, 2, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + typeof function() { + function fooVar(x) { + return x + 1; + } + }(); + `, + output: unIndent` + typeof function() { + function fooVar(x) { + return x + 1; + } + }(); + `, + options: [2, { outerIIFEBody: 2 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Keyword], + [3, 4, 6, AST_TOKEN_TYPES.Keyword], + [4, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + { + \t!function(x) { + \t\t\t\treturn x + 1; + \t}() + }; + `, + output: unIndent` + { + \t!function(x) { + \t\treturn x + 1; + \t}() + }; + `, + options: ['tab', { outerIIFEBody: 3 }], + errors: expectedErrors('tab', [[3, 2, 4, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + Buffer + .toString() + `, + output: unIndent` + Buffer + .toString() + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + Buffer + .indexOf('a') + .toString() + `, + output: unIndent` + Buffer + .indexOf('a') + .toString() + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[3, 4, 0, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + Buffer. + length + `, + output: unIndent` + Buffer. + length + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([[2, 4, 0, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + Buffer. + \t\tlength + `, + output: unIndent` + Buffer. + \tlength + `, + options: ['tab', { MemberExpression: 1 }], + errors: expectedErrors('tab', [[2, 1, 2, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + Buffer + .foo + .bar + `, + output: unIndent` + Buffer + .foo + .bar + `, + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + new + .target + } + `, + output: unIndent` + function foo() { + new + .target + } + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function foo() { + new. + target + } + `, + output: unIndent` + function foo() { + new. + target + } + `, + errors: expectedErrors([3, 8, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + // Indentation with multiple else statements: https://github.com/eslint/eslint/issues/6956 + + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (qux) qux(); + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (qux) qux(); + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else qux(); + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else qux(); + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + foo(); + if (baz) foobar(); + else qux(); + `, + output: unIndent` + foo(); + if (baz) foobar(); + else qux(); + `, + options: [2], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Keyword], + [3, 0, 2, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (bip) { + qux(); + } + `, + output: unIndent` + if (foo) bar(); + else if (baz) foobar(); + else if (bip) { + qux(); + } + `, + options: [2], + errors: expectedErrors([ + [3, 0, 5, AST_TOKEN_TYPES.Keyword], + [4, 2, 7, AST_TOKEN_TYPES.Identifier], + [5, 0, 5, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if (foo) bar(); + else if (baz) { + foobar(); + } else if (boop) { + qux(); + } + `, + output: unIndent` + if (foo) bar(); + else if (baz) { + foobar(); + } else if (boop) { + qux(); + } + `, + options: [2], + errors: expectedErrors([ + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 5, AST_TOKEN_TYPES.Punctuator], + [5, 2, 7, AST_TOKEN_TYPES.Identifier], + [6, 0, 5, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 1, body: 2 } }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Identifier], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + output: unIndent` + function foo(aaa, bbb, + ccc, ddd) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 3, body: 1 } }], + errors: expectedErrors([ + [2, 6, 2, AST_TOKEN_TYPES.Identifier], + [3, 2, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [4, { FunctionDeclaration: { parameters: 1, body: 3 } }], + errors: expectedErrors([ + [2, 4, 8, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 12, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + output: unIndent` + function foo(aaa, + bbb, ccc, + ddd, eee, fff) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 1 } }], + errors: expectedErrors([ + [2, 13, 2, AST_TOKEN_TYPES.Identifier], + [3, 13, 19, AST_TOKEN_TYPES.Identifier], + [4, 2, 3, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + output: unIndent` + function foo(aaa, bbb) + { + bar(); + } + `, + options: [2, { FunctionDeclaration: { body: 3 } }], + errors: expectedErrors([3, 6, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + output: unIndent` + function foo( + aaa, + bbb) { + bar(); + } + `, + options: [2, { FunctionDeclaration: { parameters: 'first', body: 2 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, + ccc, + ddd) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 2, body: 0 } }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 4, 6, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, + ccc) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 1, body: 10 } }], + errors: expectedErrors([ + [2, 2, 3, AST_TOKEN_TYPES.Identifier], + [3, 2, 1, AST_TOKEN_TYPES.Identifier], + [4, 20, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + output: unIndent` + var foo = function(aaa, + bbb, ccc, ddd, + eee, fff) { + bar(); + } + `, + options: [4, { FunctionExpression: { parameters: 'first', body: 1 } }], + errors: expectedErrors([ + [2, 19, 2, AST_TOKEN_TYPES.Identifier], + [3, 19, 24, AST_TOKEN_TYPES.Identifier], + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + output: unIndent` + var foo = function( + aaa, bbb, ccc, + ddd, eee) { + bar(); + } + `, + options: [2, { FunctionExpression: { parameters: 'first', body: 3 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 6, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = bar; + \t\t\tvar baz = qux; + `, + output: unIndent` + var foo = bar; + var baz = qux; + `, + options: [2], + errors: expectedErrors([ + 2, + '0 spaces', + '3 tabs', + AST_TOKEN_TYPES.Keyword, + ]), + }, + { + code: unIndent` + function foo() { + \tbar(); + baz(); + qux(); + } + `, + output: unIndent` + function foo() { + \tbar(); + \tbaz(); + \tqux(); + } + `, + options: ['tab'], + errors: expectedErrors('tab', [ + [3, '1 tab', '2 spaces', AST_TOKEN_TYPES.Identifier], + [4, '1 tab', '14 spaces', AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo() { + bar(); + \t\t} + `, + output: unIndent` + function foo() { + bar(); + } + `, + options: [2], + errors: expectedErrors([ + [3, '0 spaces', '2 tabs', AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + output: unIndent` + function foo() { + function bar() { + baz(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1 } }], + errors: expectedErrors([3, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + output: unIndent` + function foo() { + function bar(baz, + qux) { + foobar(); + } + } + `, + options: [2, { FunctionDeclaration: { body: 1, parameters: 2 } }], + errors: expectedErrors([3, 6, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + output: unIndent` + function foo() { + var bar = function(baz, + qux) { + foobar(); + }; + } + `, + options: [2, { FunctionExpression: { parameters: 3 } }], + errors: expectedErrors([3, 8, 10, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + output: unIndent` + foo.bar( + baz, qux, function() { + qux; + } + ); + `, + options: [ + 2, + { FunctionExpression: { body: 3 }, CallExpression: { arguments: 3 } }, + ], + errors: expectedErrors([3, 12, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + { + try { + } + catch (err) { + } + finally { + } + } + `, + output: unIndent` + { + try { + } + catch (err) { + } + finally { + } + } + `, + errors: expectedErrors([ + [4, 4, 0, AST_TOKEN_TYPES.Keyword], + [6, 4, 0, AST_TOKEN_TYPES.Keyword], + ]), + }, + { + code: unIndent` + { + do { + } + while (true) + } + `, + output: unIndent` + { + do { + } + while (true) + } + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + function foo() { + return ( + 1 + ) + } + `, + output: unIndent` + function foo() { + return ( + 1 + ) + } + `, + options: [2], + errors: expectedErrors([[4, 2, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + function foo() { + return ( + 1 + ); + } + `, + output: unIndent` + function foo() { + return ( + 1 + ); + } + `, + options: [2], + errors: expectedErrors([[4, 2, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + output: unIndent` + function test(){ + switch(length){ + case 1: return function(a){ + return fn.call(that, a); + }; + } + } + `, + options: [2, { VariableDeclarator: 2, SwitchCase: 1 }], + errors: expectedErrors([[4, 6, 4, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + function foo() { + return 1 + } + `, + output: unIndent` + function foo() { + return 1 + } + `, + options: [2], + errors: expectedErrors([[2, 2, 3, AST_TOKEN_TYPES.Keyword]]), + }, + { + code: unIndent` + foo( + bar, + baz, + qux); + `, + output: unIndent` + foo( + bar, + baz, + qux); + `, + options: [2, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo( + \tbar, + \tbaz); + `, + output: unIndent` + foo( + bar, + baz); + `, + options: [2, { CallExpression: { arguments: 2 } }], + errors: expectedErrors([ + [2, '4 spaces', '1 tab', AST_TOKEN_TYPES.Identifier], + [3, '4 spaces', '1 tab', AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, + \t\tbaz, + \t\tqux); + `, + output: unIndent` + foo(bar, + \tbaz, + \tqux); + `, + options: ['tab', { CallExpression: { arguments: 1 } }], + errors: expectedErrors('tab', [ + [2, 1, 2, AST_TOKEN_TYPES.Identifier], + [3, 1, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, baz, + qux); + `, + output: unIndent` + foo(bar, baz, + qux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([2, 4, 9, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo( + bar, + baz); + `, + output: unIndent` + foo( + bar, + baz); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([ + [2, 2, 10, AST_TOKEN_TYPES.Identifier], + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + output: unIndent` + foo(bar, + 1 + 2, + !baz, + new Car('!') + ); + `, + options: [2, { CallExpression: { arguments: 3 } }], + errors: expectedErrors([ + [2, 6, 2, AST_TOKEN_TYPES.Numeric], + [3, 6, 14, AST_TOKEN_TYPES.Punctuator], + [4, 6, 8, AST_TOKEN_TYPES.Keyword], + ]), + }, + + // https://github.com/eslint/eslint/issues/7573 + { + code: unIndent` + return ( + foo + ); + `, + output: unIndent` + return ( + foo + ); + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + return ( + foo + ) + `, + output: unIndent` + return ( + foo + ) + `, + parserOptions: { ecmaFeatures: { globalReturn: true } }, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + + // https://github.com/eslint/eslint/issues/7604 + { + code: unIndent` + if (foo) { + /* comment */bar(); + } + `, + output: unIndent` + if (foo) { + /* comment */bar(); + } + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + foo('bar', + /** comment */{ + ok: true + }); + `, + output: unIndent` + foo('bar', + /** comment */{ + ok: true + }); + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` + foo( + (bar) + ); + `, + output: unIndent` + foo( + (bar) + ); + `, + options: [4, { CallExpression: { arguments: 1 } }], + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + (( + foo + )) + `, + output: unIndent` + (( + foo + )) + `, + options: [4], + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + + // ternary expressions (https://github.com/eslint/eslint/issues/7420) + { + code: unIndent` + foo + ? bar + : baz + `, + output: unIndent` + foo + ? bar + : baz + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Punctuator], + [3, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ + foo ? + bar : + baz, + qux + ] + `, + output: unIndent` + [ + foo ? + bar : + baz, + qux + ] + `, + errors: expectedErrors([5, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + /* + * Checking comments: + * https://github.com/eslint/eslint/issues/6571 + */ + code: unIndent` + foo(); + // comment + /* multiline + comment */ + bar(); + // trailing comment + `, + output: unIndent` + foo(); + // comment + /* multiline + comment */ + bar(); + // trailing comment + `, + options: [2], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Line], + [3, 0, 4, AST_TOKEN_TYPES.Block], + [6, 0, 1, AST_TOKEN_TYPES.Line], + ]), + }, + { + code: ' // comment', + output: '// comment', + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + foo + // comment + `, + output: unIndent` + foo + // comment + `, + errors: expectedErrors([2, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + // comment + foo + `, + output: unIndent` + // comment + foo + `, + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + [ + // no elements + ] + `, + output: unIndent` + [ + // no elements + ] + `, + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Line]), + }, + { + /* + * Destructuring assignments: + * https://github.com/eslint/eslint/issues/6813 + */ + code: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + output: unIndent` + var { + foo, + bar, + baz: qux, + foobar: baz = foobar + } = qux; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 2, 6, AST_TOKEN_TYPES.Identifier], + [6, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const { + a + } = { + a: 1 + } + `, + output: unIndent` + const { + a + } = { + a: 1 + } + `, + options: [2], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var foo = [ + bar, + baz + ] + `, + output: unIndent` + var foo = [ + bar, + baz + ] + `, + errors: expectedErrors([ + [2, 4, 11, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 0, 10, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 8 }], + errors: expectedErrors([ + [2, 16, 2, AST_TOKEN_TYPES.Identifier], + [3, 16, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, + qux + ] + `, + output: unIndent` + var foo = [bar, + baz, + qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + errors: expectedErrors([ + [2, 11, 4, AST_TOKEN_TYPES.Identifier], + [3, 11, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = [bar, + baz, qux + ] + `, + output: unIndent` + var foo = [bar, + baz, qux + ] + `, + options: [2, { ArrayExpression: 'first' }], + errors: expectedErrors([2, 11, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + qux: 4 } + ] + `, + output: unIndent` + var foo = [ + { bar: 1, + baz: 2 }, + { bar: 3, + qux: 4 } + ] + `, + options: [4, { ArrayExpression: 2, ObjectExpression: 'first' }], + errors: expectedErrors([ + [3, 10, 12, AST_TOKEN_TYPES.Identifier], + [5, 10, 12, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + output: unIndent` + var foo = { + bar: 1, + baz: 2 + }; + `, + options: [2, { ObjectExpression: 0 }], + errors: expectedErrors([ + [2, 0, 2, AST_TOKEN_TYPES.Identifier], + [3, 0, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var quux = { foo: 1, bar: 2, + baz: 3 } + `, + output: unIndent` + var quux = { foo: 1, bar: 2, + baz: 3 } + `, + options: [2, { ObjectExpression: 'first' }], + errors: expectedErrors([2, 13, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + function foo() { + [ + foo + ] + } + `, + output: unIndent` + function foo() { + [ + foo + ] + } + `, + options: [2, { ArrayExpression: 4 }], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Punctuator], + [3, 10, 12, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + output: unIndent` + var [ + foo, + bar, + baz, + foobar = baz + ] = qux; + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 2, 6, AST_TOKEN_TYPES.Identifier], + [6, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + output: unIndent` + import { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + output: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [4, { ImportDeclaration: 'first' }], + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([[3, 9, 10, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + output: unIndent` + import { foo, + bar, + baz, + } from 'qux'; + `, + options: [2, { ImportDeclaration: 2 }], + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([[3, 4, 5, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + }; + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + }; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } from 'qux'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7233 + code: unIndent` + var folder = filePath + .foo() + .bar; + `, + output: unIndent` + var folder = filePath + .foo() + .bar; + `, + options: [2, { MemberExpression: 2 }], + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Punctuator], + [3, 4, 6, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + for (const foo of bar) + baz(); + `, + output: unIndent` + for (const foo of bar) + baz(); + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + var x = () => + 5; + `, + output: unIndent` + var x = () => + 5; + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Numeric]), + }, + { + // BinaryExpressions with parens + code: unIndent` + foo && ( + bar + ) + `, + output: unIndent` + foo && ( + bar + ) + `, + options: [4], + errors: expectedErrors([2, 4, 8, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo && + !bar( + ) + `, + output: unIndent` + foo && + !bar( + ) + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + output: unIndent` + foo && + ![].map(() => { + bar(); + }) + `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ + ] || [ + ] + `, + output: unIndent` + [ + ] || [ + ] + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo + || ( + bar + ) + `, + output: unIndent` + foo + || ( + bar + ) + `, + errors: expectedErrors([ + [3, 12, 16, AST_TOKEN_TYPES.Identifier], + [4, 8, 12, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + 1 + + ( + 1 + ) + `, + output: unIndent` + 1 + + ( + 1 + ) + `, + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Numeric], + [4, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + + // Template curlies + { + code: unIndent` + \`foo\${ + bar}\` + `, + output: unIndent` + \`foo\${ + bar}\` + `, + options: [2], + errors: expectedErrors([2, 2, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + output: unIndent` + \`foo\${ + \`bar\${ + baz}\`}\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Template], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + output: unIndent` + \`foo\${ + \`bar\${ + baz + }\` + }\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Template], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 2, 4, AST_TOKEN_TYPES.Template], + [5, 0, 2, AST_TOKEN_TYPES.Template], + ]), + }, + { + code: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + output: unIndent` + \`foo\${ + ( + bar + ) + }\` + `, + options: [2], + errors: expectedErrors([ + [2, 2, 0, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Identifier], + [4, 2, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + output: unIndent` + function foo() { + \`foo\${bar}baz\${ + qux}foo\${ + bar}baz\` + } + `, + errors: expectedErrors([ + [3, 8, 0, AST_TOKEN_TYPES.Identifier], + [4, 8, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + function foo() { + const template = \`the indentation of + a curly element in a \${ + node.type + } node is checked.\`; + } + `, + output: unIndent` + function foo() { + const template = \`the indentation of + a curly element in a \${ + node.type + } node is checked.\`; + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Template], + ]), + }, + { + code: unIndent` + function foo() { + const template = \`this time the + closing curly is at the end of the line \${ + foo} + so the spaces before this line aren't removed.\`; + } + `, + output: unIndent` + function foo() { + const template = \`this time the + closing curly is at the end of the line \${ + foo} + so the spaces before this line aren't removed.\`; + } + `, + errors: expectedErrors([4, 4, 12, AST_TOKEN_TYPES.Identifier]), + }, + { + /* + * https://github.com/eslint/eslint/issues/1801 + * Note: This issue also mentioned checking the indentation for the 2 below. However, + * this is intentionally ignored because everyone seems to have a different idea of how + * BinaryExpressions should be indented. + */ + code: unIndent` + if (true) { + a = ( + 1 + + 2); + } + `, + output: unIndent` + if (true) { + a = ( + 1 + + 2); + } + `, + errors: expectedErrors([3, 8, 0, AST_TOKEN_TYPES.Numeric]), + }, + { + // https://github.com/eslint/eslint/issues/3737 + code: unIndent` + if (true) { + for (;;) { + b(); + } + } + `, + output: unIndent` + if (true) { + for (;;) { + b(); + } + } + `, + options: [2], + errors: expectedErrors([ + [2, 2, 4, AST_TOKEN_TYPES.Keyword], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + // https://github.com/eslint/eslint/issues/6670 + code: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + output: unIndent` + function f() { + return asyncCall() + .then( + 'some string', + [ + 1, + 2, + 3 + ] + ); + } + `, + options: [4, { MemberExpression: 1, CallExpression: { arguments: 1 } }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 15, AST_TOKEN_TYPES.String], + [5, 12, 14, AST_TOKEN_TYPES.Punctuator], + [6, 16, 14, AST_TOKEN_TYPES.Numeric], + [7, 16, 9, AST_TOKEN_TYPES.Numeric], + [8, 16, 35, AST_TOKEN_TYPES.Numeric], + [9, 12, 22, AST_TOKEN_TYPES.Punctuator], + [10, 8, 0, AST_TOKEN_TYPES.Punctuator], + [11, 0, 1, AST_TOKEN_TYPES.Punctuator], + ]), + }, + + // https://github.com/eslint/eslint/issues/7242 + { + code: unIndent` + var x = [ + [1], + [2] + ] + `, + output: unIndent` + var x = [ + [1], + [2] + ] + `, + errors: expectedErrors([ + [2, 4, 6, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + output: unIndent` + var y = [ + {a: 1}, + {b: 2} + ] + `, + errors: expectedErrors([ + [2, 4, 6, AST_TOKEN_TYPES.Punctuator], + [3, 4, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + output: unIndent` + echo = spawn('cmd.exe', + ['foo', 'bar', + 'baz']); + `, + options: [ + 2, + { ArrayExpression: 'first', CallExpression: { arguments: 'first' } }, + ], + errors: expectedErrors([ + [2, 13, 12, AST_TOKEN_TYPES.Punctuator], + [3, 14, 13, AST_TOKEN_TYPES.String], + ]), + }, + { + // https://github.com/eslint/eslint/issues/7522 + code: unIndent` + foo( + ) + `, + output: unIndent` + foo( + ) + `, + errors: expectedErrors([2, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + // https://github.com/eslint/eslint/issues/7616 + code: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + output: unIndent` + foo( + bar, + { + baz: 1 + } + ) + `, + options: [4, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([[2, 4, 8, AST_TOKEN_TYPES.Identifier]]), + }, + { + code: ' new Foo', + output: 'new Foo', + errors: expectedErrors([1, 0, 2, AST_TOKEN_TYPES.Keyword]), + }, + { + code: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + output: unIndent` + var foo = 0, bar = 0, baz = 0; + export { + foo, + bar, + baz + } + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo + ? bar + : baz + `, + output: unIndent` + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo ? + bar : + baz + `, + output: unIndent` + foo ? + bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo ? + bar + : baz + `, + output: unIndent` + foo ? + bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo + ? bar : + baz + `, + output: unIndent` + foo + ? bar : + baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + output: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Punctuator], + [4, 4, 12, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + output: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 8, AST_TOKEN_TYPES.Identifier], + [4, 4, 12, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + output: unIndent` + var a = + foo ? bar : + baz ? qux : + foobar ? boop : + /*else*/ beep + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + [4, 4, 2, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + var a = + foo + ? bar + : baz + `, + output: unIndent` + var a = + foo + ? bar + : baz + `, + options: [4, { flatTernaryExpressions: true }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + output: unIndent` + foo ? bar + : baz ? qux + : foobar ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + output: unIndent` + foo ? bar : + baz ? qux : + foobar ? boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Identifier], + [4, 12, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + output: unIndent` + foo + ? bar + : baz + ? qux + : foobar + ? boop + : beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + [5, 8, 4, AST_TOKEN_TYPES.Punctuator], + [6, 12, 4, AST_TOKEN_TYPES.Punctuator], + [7, 12, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + output: unIndent` + foo ? + bar : + baz ? + qux : + foobar ? + boop : + beep + `, + options: [4, { flatTernaryExpressions: false }], + errors: expectedErrors([ + [4, 8, 4, AST_TOKEN_TYPES.Identifier], + [5, 8, 4, AST_TOKEN_TYPES.Identifier], + [6, 12, 4, AST_TOKEN_TYPES.Identifier], + [7, 12, 4, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + output: unIndent` + foo.bar('baz', function(err) { + qux; + }); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([2, 2, 10, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + output: unIndent` + foo.bar(function() { + cookies; + }).baz(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + output: unIndent` + foo.bar().baz(function() { + cookies; + }).qux(function() { + cookies; + }); + `, + options: [2, { MemberExpression: 1 }], + errors: expectedErrors([ + [4, 2, 4, AST_TOKEN_TYPES.Identifier], + [5, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + output: unIndent` + [ foo, + bar ].forEach(function() { + baz; + }) + `, + options: [2, { ArrayExpression: 'first', MemberExpression: 1 }], + errors: expectedErrors([ + [3, 2, 4, AST_TOKEN_TYPES.Identifier], + [4, 0, 2, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo[ + bar + ]; + `, + output: unIndent` + foo[ + bar + ]; + `, + options: [4, { MemberExpression: 1 }], + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo({ + bar: 1, + baz: 2 + }) + `, + output: unIndent` + foo({ + bar: 1, + baz: 2 + }) + `, + options: [4, { ObjectExpression: 'first' }], + errors: expectedErrors([ + [2, 4, 0, AST_TOKEN_TYPES.Identifier], + [3, 4, 0, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + foo( + bar, baz, + qux); + `, + output: unIndent` + foo( + bar, baz, + qux); + `, + options: [2, { CallExpression: { arguments: 'first' } }], + errors: expectedErrors([ + [2, 2, 24, AST_TOKEN_TYPES.Identifier], + [3, 2, 24, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + output: unIndent` + if (foo) bar() + + ; [1, 2, 3].map(baz) + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + if (foo) + ; + `, + output: unIndent` + if (foo) + ; + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + import {foo} + from 'bar'; + `, + output: unIndent` + import {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + export {foo} + from 'bar'; + `, + output: unIndent` + export {foo} + from 'bar'; + `, + parserOptions: { sourceType: 'module' }, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + ( + a + ) => b => { + c + } + `, + output: unIndent` + ( + a + ) => b => { + c + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + ( + a + ) => b => c => d => { + e + } + `, + output: unIndent` + ( + a + ) => b => c => d => { + e + } + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + if ( + foo + ) bar( + baz + ); + `, + output: unIndent` + if ( + foo + ) bar( + baz + ); + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + ( + foo + )( + bar + ) + `, + output: unIndent` + ( + foo + )( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (() => + foo + )( + bar + ) + `, + output: unIndent` + (() => + foo + )( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + (() => { + foo(); + })( + bar + ) + `, + output: unIndent` + (() => { + foo(); + })( + bar + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo. + bar. + baz + `, + output: unIndent` + foo. + bar. + baz + `, + errors: expectedErrors([ + [2, 4, 2, AST_TOKEN_TYPES.Identifier], + [3, 4, 6, AST_TOKEN_TYPES.Identifier], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName + = (baz( + 'bar', + 'bar' + )); + `, + output: unIndent` + const foo = a.b(), + longName + = (baz( + 'bar', + 'bar' + )); + `, + errors: expectedErrors([ + [4, 8, 12, AST_TOKEN_TYPES.String], + [5, 8, 12, AST_TOKEN_TYPES.String], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + output: unIndent` + const foo = a.b(), + longName = + (baz( + 'bar', + 'bar' + )); + `, + errors: expectedErrors([ + [4, 8, 12, AST_TOKEN_TYPES.String], + [5, 8, 12, AST_TOKEN_TYPES.String], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + const foo = a.b(), + longName + =baz( + 'bar', + 'bar' + ); + `, + output: unIndent` + const foo = a.b(), + longName + =baz( + 'bar', + 'bar' + ); + `, + errors: expectedErrors([[6, 8, 4, AST_TOKEN_TYPES.Punctuator]]), + }, + { + code: unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + output: unIndent` + const foo = a.b(), + longName + =( + 'fff' + ); + `, + errors: expectedErrors([[4, 12, 8, AST_TOKEN_TYPES.String]]), + }, + + //---------------------------------------------------------------------- + // JSX tests + // Some of the following tests are adapted from the the tests in eslint-plugin-react. + // License: https://github.com/yannickcr/eslint-plugin-react/blob/7ca9841f22d599f447a27ef5b2a97def9229d6c8/LICENSE + //---------------------------------------------------------------------- + + { + code: unIndent` + + + + `, + output: unIndent` + + + + `, + errors: expectedErrors([2, 4, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + `, + output: unIndent` + + + + `, + options: [2], + errors: expectedErrors([2, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + `, + output: unIndent` + + \t + + `, + options: ['tab'], + errors: expectedErrors([ + 2, + '1 tab', + '4 spaces', + AST_TOKEN_TYPES.Punctuator, + ]), + }, + { + code: unIndent` + function App() { + return + + ; + } + `, + output: unIndent` + function App() { + return + + ; + } + `, + options: [2], + errors: expectedErrors([4, 2, 9, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function App() { + return ( + + ); + } + `, + output: unIndent` + function App() { + return ( + + ); + } + `, + options: [2], + errors: expectedErrors([4, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + function App() { + return ( + + + + ); + } + `, + output: unIndent` + function App() { + return ( + + + + ); + } + `, + options: [2], + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Punctuator], + [4, 6, 2, AST_TOKEN_TYPES.Punctuator], + [5, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + + {test} + + `, + output: unIndent` + + {test} + + `, + errors: expectedErrors([2, 4, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + {options.map((option, index) => ( + + ))} + + `, + output: unIndent` + + {options.map((option, index) => ( + + ))} + + `, + errors: expectedErrors([4, 12, 11, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + [ +
, +
+ ] + `, + output: unIndent` + [ +
, +
+ ] + `, + options: [2], + errors: expectedErrors([3, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + + + + + `, + output: unIndent` + + + \t + + + `, + options: ['tab'], + errors: expectedErrors([ + 3, + '1 tab', + '1 space', + AST_TOKEN_TYPES.Punctuator, + ]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression) + */ + code: unIndent` + foo ? + : + + `, + output: unIndent` + foo ? + : + + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + /* + * Multiline ternary + * (colon on its own line) + */ + code: unIndent` + foo ? + + : + + `, + output: unIndent` + foo ? + + : + + `, + errors: expectedErrors([ + [3, 4, 0, AST_TOKEN_TYPES.Punctuator], + [4, 4, 0, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + /* + * Multiline ternary + * (colon at the end of the first expression, parenthesized first expression) + */ + code: unIndent` + foo ? ( + + ) : + + `, + output: unIndent` + foo ? ( + + ) : + + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + errors: expectedErrors([2, 4, 2, AST_TOKEN_TYPES.JSXIdentifier]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: [2], + errors: expectedErrors([3, 0, 2, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + const Button = function(props) { + return ( + + ); + }; + `, + output: unIndent` + const Button = function(props) { + return ( + + ); + }; + `, + options: [2], + errors: expectedErrors([6, 4, 36, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = function() { + return + } + `, + output: unIndent` + var x = function() { + return + } + `, + options: [2], + errors: expectedErrors([4, 2, 9, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = + `, + output: unIndent` + var x = + `, + options: [2], + errors: expectedErrors([3, 0, 8, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + var x = ( + + ) + `, + output: unIndent` + var x = ( + + ) + `, + options: [2], + errors: expectedErrors([3, 2, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: ['tab'], + errors: expectedErrors('tab', [3, 0, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + + `, + output: unIndent` + + `, + options: ['tab'], + errors: expectedErrors('tab', [3, 0, 1, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + < + foo + .bar + .baz + > + foo + + `, + output: unIndent` + < + foo + .bar + .baz + > + foo + + `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 8, 4, AST_TOKEN_TYPES.Punctuator], + [9, 8, 4, AST_TOKEN_TYPES.JSXIdentifier], + [10, 8, 4, AST_TOKEN_TYPES.JSXIdentifier], + ]), + }, + { + code: unIndent` + < + input + type= + "number" + /> + `, + output: unIndent` + < + input + type= + "number" + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.JSXText]), + }, + { + code: unIndent` + < + input + type= + {'number'} + /> + `, + output: unIndent` + < + input + type= + {'number'} + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + < + input + type + ="number" + /> + `, + output: unIndent` + < + input + type + ="number" + /> + `, + errors: expectedErrors([4, 8, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + output: unIndent` + foo ? ( + bar + ) : ( + baz + ) + `, + errors: expectedErrors([ + [4, 4, 8, AST_TOKEN_TYPES.Identifier], + [5, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + output: unIndent` + foo ? ( +
+
+ ) : ( + + + ) + `, + errors: expectedErrors([ + [5, 4, 8, AST_TOKEN_TYPES.Punctuator], + [6, 4, 8, AST_TOKEN_TYPES.Punctuator], + [7, 0, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` +
+ { + ( + 1 + ) + } +
+ `, + output: unIndent` +
+ { + ( + 1 + ) + } +
+ `, + errors: expectedErrors([ + [3, 8, 4, AST_TOKEN_TYPES.Punctuator], + [4, 12, 8, AST_TOKEN_TYPES.Numeric], + [5, 8, 4, AST_TOKEN_TYPES.Punctuator], + ]), + }, + { + code: unIndent` +
+ { + /* foo */ + } +
+ `, + output: unIndent` +
+ { + /* foo */ + } +
+ `, + errors: expectedErrors([3, 8, 6, AST_TOKEN_TYPES.Block]), + }, + { + code: unIndent` +
foo +
bar
+
+ `, + output: unIndent` +
foo +
bar
+
+ `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + Foo bar  + baz qux. + + `, + output: unIndent` + Foo bar  + baz qux. + + `, + errors: expectedErrors([2, 4, 0, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ({ + foo + }: bar) => baz + `, + output: unIndent` + ({ + foo + }: bar) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ([ + foo + ]: bar) => baz + `, + output: unIndent` + ([ + foo + ]: bar) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + ({ + foo + }: {}) => baz + `, + output: unIndent` + ({ + foo + }: {}) => baz + `, + errors: expectedErrors([3, 0, 4, AST_TOKEN_TYPES.Punctuator]), + }, + { + code: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + output: unIndent` + class Foo { + foo() { + bar(); + } + } + `, + options: [4, { ignoredNodes: ['ClassBody'] }], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + $(function() { + + foo(); + bar(); + + foo(function() { + baz(); + }); + + }); + `, + output: unIndent` + $(function() { + + foo(); + bar(); + + foo(function() { + baz(); + }); + + }); + `, + options: [ + 4, + { + ignoredNodes: [ + "ExpressionStatement > CallExpression[callee.name='$'] > FunctionExpression > BlockStatement", + ], + }, + ], + errors: expectedErrors([7, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + (function($) { + $(function() { + foo; + }); + })() + `, + output: unIndent` + (function($) { + $(function() { + foo; + }); + })() + `, + options: [ + 4, + { + ignoredNodes: [ + 'ExpressionStatement > CallExpression > FunctionExpression.callee > BlockStatement', + ], + }, + ], + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Identifier]), + }, + { + code: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + output: unIndent` + if (foo) { + doSomething(); + + // Intentionally unindented comment + doSomethingElse(); + } + `, + options: [4, { ignoreComments: false }], + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + output: unIndent` + if (foo) { + doSomething(); + + /* Intentionally unindented comment */ + doSomethingElse(); + } + `, + options: [4, { ignoreComments: false }], + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.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, AST_TOKEN_TYPES.Numeric]), + }, + + //---------------------------------------------------------------------- + // Comment alignment tests + //---------------------------------------------------------------------- + { + code: unIndent` + if (foo) { + + // Comment cannot align with code immediately above if there is a whitespace gap + doSomething(); + } + `, + output: unIndent` + if (foo) { + + // Comment cannot align with code immediately above if there is a whitespace gap + doSomething(); + } + `, + errors: expectedErrors([3, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + if (foo) { + foo( + bar); + // Comment cannot align with code immediately below if there is a whitespace gap + + } + `, + output: unIndent` + if (foo) { + foo( + bar); + // Comment cannot align with code immediately below if there is a whitespace gap + + } + `, + errors: expectedErrors([4, 4, 0, AST_TOKEN_TYPES.Line]), + }, + { + code: unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + output: unIndent` + [{ + foo + }, + + // Comment between nodes + + { + bar + }]; + `, + errors: expectedErrors([5, 0, 4, AST_TOKEN_TYPES.Line]), + }, + ], +}); diff --git a/packages/eslint-plugin/tests/rules/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts similarity index 99% rename from packages/eslint-plugin/tests/rules/indent.test.ts rename to packages/eslint-plugin/tests/rules/indent/indent.test.ts index 7fb18428d9f..647cbf753fc 100644 --- a/packages/eslint-plugin/tests/rules/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent.test.ts @@ -1,9 +1,9 @@ -import rule from '../../src/rules/indent'; -import { RuleTester, RunTests, TestCaseError } from '../RuleTester'; +import rule from '../../../src/rules/indent'; +import { RuleTester, RunTests, TestCaseError } from '../../RuleTester'; import { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, -} from '../../src/util'; +} from '../../../src/util'; type MessageIds = InferMessageIdsTypeFromRule; type Options = InferOptionsTypeFromRule; diff --git a/packages/eslint-plugin/tests/rules/indent/utils.ts b/packages/eslint-plugin/tests/rules/indent/utils.ts new file mode 100644 index 00000000000..c4bf28c08fb --- /dev/null +++ b/packages/eslint-plugin/tests/rules/indent/utils.ts @@ -0,0 +1,103 @@ +// The following code is adapted from the the code in eslint. +// License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE + +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; +import { TestCaseError } from '../../RuleTester'; +import rule from '../../../src/rules/indent'; +import { InferMessageIdsTypeFromRule } from '../../../src/util'; + +type MessageIds = InferMessageIdsTypeFromRule; + +/** + * Prevents leading spaces in a multiline template literal from appearing in the resulting string + * @param strings The strings in the template literal + * @returns The template literal, with spaces removed from all lines + */ +export function unIndent(strings: TemplateStringsArray): string { + const templateValue = strings[0]; + const lines = templateValue + .replace(/^\n/u, '') + .replace(/\n\s*$/u, '') + .split('\n'); + const lineIndents = lines + .filter(line => line.trim()) + .map(line => line.match(/ */u)![0].length); + const minLineIndent = Math.min(...lineIndents); + + return lines.map(line => line.slice(minLineIndent)).join('\n'); +} + +type ProvidedError = [ + // line number + number, + // expected indent + number | string, + // actual indent + number | string, + // node type + AST_NODE_TYPES | AST_TOKEN_TYPES +]; + +function is2DProvidedErrorArr( + providedErrors?: ProvidedError | ProvidedError[], +): providedErrors is ProvidedError[] { + return !!providedErrors && Array.isArray(providedErrors[0]); +} + +/** + * Create error message object for failure cases with a single 'found' indentation type + * @param providedErrors error info + * @returns returns the error messages collection + */ +export function expectedErrors( + providedErrors: ProvidedError | ProvidedError[], +): TestCaseError[]; +/** + * Create error message object for failure cases with a single 'found' indentation type + * @param providedIndentType indent type of string or tab + * @param providedErrors error info + * @returns returns the error messages collection + */ +export function expectedErrors( + providedIndentType: string, + providedErrors: ProvidedError | ProvidedError[], +): TestCaseError[]; +export function expectedErrors( + providedIndentType: string | ProvidedError | ProvidedError[], + providedErrors?: ProvidedError | ProvidedError[], +): TestCaseError[] { + let indentType: string; + let errors: ProvidedError[]; + + if (Array.isArray(providedIndentType)) { + errors = is2DProvidedErrorArr(providedIndentType) + ? providedIndentType + : [providedIndentType]; + indentType = 'space'; + } else { + errors = is2DProvidedErrorArr(providedErrors) + ? providedErrors + : [providedErrors!]; + indentType = providedIndentType; + } + + return errors.map(err => { + const [line, expected, actual, type] = err; + + return { + messageId: 'wrongIndentation', + data: { + expected: + typeof expected === 'string' && typeof actual === 'string' + ? expected + : `${expected} ${indentType}${expected === 1 ? '' : 's'}`, + actual, + }, + type, + line, + }; + }); +} diff --git a/packages/eslint-plugin/typings/eslint-ast-util.d.ts b/packages/eslint-plugin/typings/eslint-ast-util.d.ts new file mode 100644 index 00000000000..6a144813a49 --- /dev/null +++ b/packages/eslint-plugin/typings/eslint-ast-util.d.ts @@ -0,0 +1,3 @@ +declare module 'eslint/lib/util/ast-utils' { + export function createGlobalLinebreakMatcher(): RegExp; +} diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index cf3693453f1..7ea46da7c0b 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -56,8 +56,8 @@ declare module 'eslint/lib/rules/indent' { const rule: RuleModule< 'wrongIndentation', [ - 'tab' | number, - { + ('tab' | number)?, + ({ SwitchCase?: number; VariableDeclarator?: | ElementList @@ -85,7 +85,7 @@ declare module 'eslint/lib/rules/indent' { flatTernaryExpressions?: boolean; ignoredNodes?: string[]; ignoreComments?: boolean; - } + })? ], { '*:exit'(node: TSESTree.Node): void; diff --git a/packages/eslint-plugin/typings/functional-red-black-tree.d.ts b/packages/eslint-plugin/typings/functional-red-black-tree.d.ts new file mode 100644 index 00000000000..7066de81fb3 --- /dev/null +++ b/packages/eslint-plugin/typings/functional-red-black-tree.d.ts @@ -0,0 +1,55 @@ +declare module 'functional-red-black-tree' { + class RBNode { + public readonly key: TKey; + public readonly left: RBNode; + public readonly right: RBNode; + public readonly value: TValue; + } + + class RedBlackTreeIterator { + public readonly hasNext: boolean; + public readonly hasPrev: boolean; + public readonly index: number; + public readonly key: TKey; + public readonly node: RBNode | null; + public readonly tree: RBTree; + public readonly valid: boolean; + public readonly value: TValue; + + public clone(): RedBlackTreeIterator; + public remove(): RBTree; + public update(value: TValue): RBTree; + public next(): void; + public prev(): void; + } + + class RBTree { + public begin: RedBlackTreeIterator; + public end: RedBlackTreeIterator; + public readonly keys: TKey[]; + public readonly length: number; + public root: RBNode | null; + public readonly values: TValue[]; + + public get(key: TKey): TValue; + public insert(key: TKey, value: TValue): RBTree; + public remove(key: TKey): this; + public find(key: TKey): RedBlackTreeIterator; + public forEach( + visitor: (key: TKey, value: TValue) => void, + low: TKey, + high: TKey, + ): void; + + public ge(key: TKey): RedBlackTreeIterator; + public gt(key: TKey): RedBlackTreeIterator; + public le(key: TKey): RedBlackTreeIterator; + public lt(key: TKey): RedBlackTreeIterator; + public at(position: number): RedBlackTreeIterator; + } + + function createRBTree( + compare?: (a: TKey, b: TKey) => number, + ): RBTree; + export = createRBTree; +} diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts index e32069feccf..62ef79199a2 100644 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ b/packages/eslint-plugin/typings/ts-eslint.d.ts @@ -10,15 +10,22 @@ The def is wrapped up in a fake module so that it can be used in eslint-rules.d. declare module 'ts-eslint' { import { TSESTree } from '@typescript-eslint/typescript-estree'; import { ParserServices } from '@typescript-eslint/parser'; - import { AST, Linter, Rule } from 'eslint'; + import { Linter } from 'eslint'; import { JSONSchema4 } from 'json-schema'; //#region SourceCode + interface Program extends TSESTree.Program { + comments: TSESTree.Comment[]; + tokens: TSESTree.Token[]; + loc: TSESTree.SourceLocation; + range: TSESTree.Range; + } + namespace SourceCode { export interface Config { text: string; - ast: AST.Program; + ast: Program; parserServices?: ParserServices; scopeManager?: Scope.ScopeManager; visitorKeys?: VisitorKeys; @@ -53,15 +60,15 @@ declare module 'ts-eslint' { class SourceCode { text: string; - ast: AST.Program; + ast: Program; lines: string[]; hasBOM: boolean; parserServices: ParserServices; scopeManager: Scope.ScopeManager; visitorKeys: SourceCode.VisitorKeys; + tokensAndComments: (TSESTree.Comment | TSESTree.Token)[]; - constructor(text: string, ast: AST.Program); - // eslint-disable-next-line no-dupe-class-members + constructor(text: string, ast: Program); constructor(config: SourceCode.Config); static splitLines(text: string): string[]; @@ -179,7 +186,6 @@ declare module 'ts-eslint' { beforeCount?: number, afterCount?: number, ): TSESTree.Token[]; - // eslint-disable-next-line no-dupe-class-members getTokens( node: TSESTree.Node, options: SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions, @@ -270,7 +276,7 @@ declare module 'ts-eslint' { } interface RuleFix { - range: AST.Range; + range: TSESTree.Range; text: string; } @@ -280,25 +286,25 @@ declare module 'ts-eslint' { text: string, ): RuleFix; - insertTextAfterRange(range: AST.Range, text: string): RuleFix; + insertTextAfterRange(range: TSESTree.Range, text: string): RuleFix; insertTextBefore( nodeOrToken: TSESTree.Node | TSESTree.Token, text: string, ): RuleFix; - insertTextBeforeRange(range: AST.Range, text: string): RuleFix; + insertTextBeforeRange(range: TSESTree.Range, text: string): RuleFix; remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix; - removeRange(range: AST.Range): RuleFix; + removeRange(range: TSESTree.Range): RuleFix; replaceText( nodeOrToken: TSESTree.Node | TSESTree.Token, text: string, ): RuleFix; - replaceTextRange(range: AST.Range, text: string): RuleFix; + replaceTextRange(range: TSESTree.Range, text: string): RuleFix; } type ReportFixFunction = ( @@ -411,6 +417,7 @@ declare module 'ts-eslint' { ArrayPattern?: RuleFunction; ArrowFunctionExpression?: RuleFunction; AssignmentPattern?: RuleFunction; + AssignmentExpression?: RuleFunction; AwaitExpression?: RuleFunction; BlockStatement?: RuleFunction; BreakStatement?: RuleFunction; diff --git a/packages/typescript-estree/src/ts-estree/ast-node-types.ts b/packages/typescript-estree/src/ts-estree/ast-node-types.ts index a1e06027639..4ae1b5dcf86 100644 --- a/packages/typescript-estree/src/ts-estree/ast-node-types.ts +++ b/packages/typescript-estree/src/ts-estree/ast-node-types.ts @@ -175,4 +175,8 @@ export enum AST_TOKEN_TYPES { RegularExpression = 'RegularExpression', String = 'String', Template = 'Template', + + // comment types + Block = 'Block', + Line = 'Line', } diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index da1523fabc5..7043cafa046 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -20,6 +20,7 @@ export interface SourceLocation { */ end: LineAndColumnData; } +export type Range = [number, number]; export interface BaseNode { /** @@ -31,7 +32,7 @@ export interface BaseNode { * Both numbers are a 0-based index which is the position in the array of source code characters. * The first is the start position of the node, the second is the end position of the node. */ - range: [number, number]; + range: Range; /** * The parent node of the current node */ From a7a03ce39f82fac47e9ef1ef052ee1366eeb8c8a Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Fri, 10 May 2019 09:10:04 -0700 Subject: [PATCH 09/41] feat: Move shared types into their own package (#425) --- .eslintignore | 1 - .eslintrc.json | 1 + packages/eslint-plugin/package.json | 2 +- .../src/rules/adjacent-overload-signatures.ts | 5 +- .../eslint-plugin/src/rules/array-type.ts | 2 +- .../eslint-plugin/src/rules/await-thenable.ts | 8 +- packages/eslint-plugin/src/rules/ban-types.ts | 9 +- packages/eslint-plugin/src/rules/camelcase.ts | 5 +- .../src/rules/class-name-casing.ts | 5 +- .../rules/explicit-function-return-type.ts | 7 +- .../rules/explicit-member-accessibility.ts | 5 +- .../src/rules/func-call-spacing.ts | 2 +- .../indent-new-do-not-use/BinarySearchTree.ts | 4 +- .../indent-new-do-not-use/OffsetStorage.ts | 4 +- .../rules/indent-new-do-not-use/TokenInfo.ts | 7 +- .../src/rules/indent-new-do-not-use/index.ts | 12 +- packages/eslint-plugin/src/rules/indent.ts | 5 +- .../src/rules/member-delimiter-style.ts | 5 +- .../eslint-plugin/src/rules/member-naming.ts | 2 +- .../src/rules/member-ordering.ts | 5 +- .../src/rules/no-array-constructor.ts | 5 +- .../src/rules/no-extra-parens.ts | 9 +- .../src/rules/no-extraneous-class.ts | 5 +- .../src/rules/no-inferrable-types.ts | 5 +- .../src/rules/no-magic-numbers.ts | 5 +- .../eslint-plugin/src/rules/no-misused-new.ts | 5 +- .../eslint-plugin/src/rules/no-namespace.ts | 5 +- .../rules/no-object-literal-type-assertion.ts | 5 +- .../src/rules/no-parameter-properties.ts | 5 +- .../src/rules/no-require-imports.ts | 2 +- .../eslint-plugin/src/rules/no-this-alias.ts | 5 +- .../eslint-plugin/src/rules/no-type-alias.ts | 9 +- .../src/rules/no-unnecessary-qualifier.ts | 2 +- .../rules/no-unnecessary-type-assertion.ts | 2 +- .../eslint-plugin/src/rules/no-unused-vars.ts | 5 +- .../src/rules/no-use-before-define.ts | 29 +- .../src/rules/no-useless-constructor.ts | 5 +- .../src/rules/no-var-requires.ts | 2 +- .../eslint-plugin/src/rules/prefer-for-of.ts | 9 +- .../src/rules/prefer-function-type.ts | 4 +- .../src/rules/prefer-includes.ts | 2 +- .../src/rules/prefer-interface.ts | 5 +- .../src/rules/prefer-namespace-keyword.ts | 2 +- .../rules/prefer-string-starts-ends-with.ts | 11 +- .../src/rules/promise-function-async.ts | 2 +- .../src/rules/require-array-sort-compare.ts | 4 +- .../src/rules/restrict-plus-operands.ts | 2 +- packages/eslint-plugin/src/rules/semi.ts | 11 +- .../src/rules/type-annotation-spacing.ts | 2 +- .../eslint-plugin/src/rules/unbound-method.ts | 8 +- .../src/rules/unified-signatures.ts | 5 +- packages/eslint-plugin/src/util/createRule.ts | 63 +- .../src/util/getParserServices.ts | 6 +- packages/eslint-plugin/src/util/index.ts | 8 +- packages/eslint-plugin/src/util/misc.ts | 16 +- packages/eslint-plugin/tests/RuleTester.ts | 126 +-- .../tests/eslint-rules/no-redeclare.test.ts | 2 +- .../tests/rules/func-call-spacing.test.ts | 17 +- .../tests/rules/indent/indent-eslint.test.ts | 2 +- .../tests/rules/indent/indent.test.ts | 10 +- .../eslint-plugin/tests/rules/indent/utils.ts | 10 +- .../tests/rules/no-array-constructor.test.ts | 2 +- .../tests/rules/no-extraneous-class.test.ts | 2 +- .../tests/rules/no-for-in-array.test.ts | 2 +- .../tests/rules/no-inferrable-types.test.ts | 8 +- .../tests/rules/no-this-alias.test.ts | 2 +- .../rules/no-unnecessary-qualifier.test.ts | 2 +- .../tests/rules/no-use-before-define.test.ts | 2 +- .../tests/rules/prefer-function-type.test.ts | 2 +- .../eslint-plugin/tests/rules/semi.test.ts | 7 +- .../rules/type-annotation-spacing.test.ts | 7 +- packages/eslint-plugin/tests/util.test.ts | 112 --- .../eslint-plugin/typings/eslint-rules.d.ts | 85 +-- .../eslint-plugin/typings/eslint-utils.d.ts | 25 +- packages/eslint-plugin/typings/ts-eslint.d.ts | 716 ------------------ packages/experimental-utils/LICENSE | 21 + packages/experimental-utils/README.md | 33 + packages/experimental-utils/jest.config.js | 13 + packages/experimental-utils/package.json | 40 + .../src/eslint-utils/RuleCreator.ts | 65 ++ .../src/eslint-utils}/applyDefault.ts | 0 .../eslint-utils/batchedSingleLineTests.ts | 59 ++ .../src/eslint-utils}/deepMerge.ts | 0 .../src/eslint-utils/index.ts | 4 + packages/experimental-utils/src/index.ts | 13 + .../experimental-utils/src/ts-eslint/AST.ts | 18 + .../src/ts-eslint/Linter.ts | 132 ++++ .../src/ts-eslint/ParserOptions.ts | 21 + .../experimental-utils/src/ts-eslint/Rule.ts | 400 ++++++++++ .../src/ts-eslint/RuleTester.ts | 76 ++ .../experimental-utils/src/ts-eslint/Scope.ts | 106 +++ .../src/ts-eslint/SourceCode.ts | 193 +++++ .../experimental-utils/src/ts-eslint/index.ts | 7 + .../tests/eslint-utils/applyDefault.test.ts | 58 ++ .../tests/eslint-utils/deepMerge.test.ts | 60 ++ .../experimental-utils/tsconfig.build.json | 9 + packages/experimental-utils/tsconfig.json | 8 + packages/parser/package.json | 2 +- packages/parser/src/analyze-scope.ts | 13 +- packages/parser/src/parser.ts | 10 +- packages/parser/src/scope/scope-manager.ts | 3 +- packages/parser/src/scope/scopes.ts | 2 +- packages/parser/tests/lib/parser.ts | 6 +- packages/parser/typings/eslint-scope.d.ts | 16 +- .../typescript-estree/src/ast-converter.ts | 4 +- .../typescript-estree/src/convert-comments.ts | 2 +- packages/typescript-estree/src/convert.ts | 5 +- packages/typescript-estree/src/node-utils.ts | 2 +- .../typescript-estree/src/parser-options.ts | 15 +- packages/typescript-estree/src/parser.ts | 34 +- .../typescript-estree/src/semantic-errors.ts | 2 +- .../typescript-estree/src/ts-estree/index.ts | 4 +- .../src/ts-estree/ts-estree.ts | 2 +- .../src/{ => ts-estree}/ts-nodes.ts | 2 +- .../typescript-estree/src/tsconfig-parser.ts | 2 +- .../tests/ast-alignment/fixtures-to-test.ts | 2 +- .../tests/ast-alignment/parse.ts | 2 +- .../tests/ast-alignment/utils.ts | 2 +- .../typescript-estree/tests/lib/comments.ts | 4 +- .../typescript-estree/tests/lib/javascript.ts | 4 +- packages/typescript-estree/tests/lib/jsx.ts | 4 +- packages/typescript-estree/tests/lib/parse.ts | 10 +- .../tests/lib/semantic-diagnostics-enabled.ts | 2 +- .../tests/lib/semanticInfo.ts | 17 +- packages/typescript-estree/tests/lib/tsx.ts | 4 +- .../typescript-estree/tests/lib/typescript.ts | 4 +- .../typescript-estree/tools/test-utils.ts | 8 +- .../typescript-estree/tsconfig.build.json | 3 +- packages/typescript-estree/tsconfig.json | 5 +- yarn.lock | 2 +- 130 files changed, 1729 insertions(+), 1313 deletions(-) delete mode 100644 packages/eslint-plugin/typings/ts-eslint.d.ts create mode 100644 packages/experimental-utils/LICENSE create mode 100644 packages/experimental-utils/README.md create mode 100644 packages/experimental-utils/jest.config.js create mode 100644 packages/experimental-utils/package.json create mode 100644 packages/experimental-utils/src/eslint-utils/RuleCreator.ts rename packages/{eslint-plugin/src/util => experimental-utils/src/eslint-utils}/applyDefault.ts (100%) create mode 100644 packages/experimental-utils/src/eslint-utils/batchedSingleLineTests.ts rename packages/{eslint-plugin/src/util => experimental-utils/src/eslint-utils}/deepMerge.ts (100%) create mode 100644 packages/experimental-utils/src/eslint-utils/index.ts create mode 100644 packages/experimental-utils/src/index.ts create mode 100644 packages/experimental-utils/src/ts-eslint/AST.ts create mode 100644 packages/experimental-utils/src/ts-eslint/Linter.ts create mode 100644 packages/experimental-utils/src/ts-eslint/ParserOptions.ts create mode 100644 packages/experimental-utils/src/ts-eslint/Rule.ts create mode 100644 packages/experimental-utils/src/ts-eslint/RuleTester.ts create mode 100644 packages/experimental-utils/src/ts-eslint/Scope.ts create mode 100644 packages/experimental-utils/src/ts-eslint/SourceCode.ts create mode 100644 packages/experimental-utils/src/ts-eslint/index.ts create mode 100644 packages/experimental-utils/tests/eslint-utils/applyDefault.test.ts create mode 100644 packages/experimental-utils/tests/eslint-utils/deepMerge.test.ts create mode 100644 packages/experimental-utils/tsconfig.build.json create mode 100644 packages/experimental-utils/tsconfig.json rename packages/typescript-estree/src/{ => ts-estree}/ts-nodes.ts (97%) diff --git a/.eslintignore b/.eslintignore index c1536a77bd3..92693b86382 100644 --- a/.eslintignore +++ b/.eslintignore @@ -5,5 +5,4 @@ fixtures shared-fixtures coverage -packages/typescript-estree/src/estree packages/eslint-plugin-tslint/tests diff --git a/.eslintrc.json b/.eslintrc.json index 91c11fbba92..00e766eaaef 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -12,6 +12,7 @@ "no-dupe-class-members": "off", "no-mixed-operators": "error", "no-console": "off", + "no-dupe-class-members": "off", "no-undef": "off", "@typescript-eslint/indent": "off", "@typescript-eslint/no-explicit-any": "off", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index e85f8489268..ae2f544fa93 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -37,7 +37,7 @@ }, "dependencies": { "@typescript-eslint/parser": "1.7.0", - "@typescript-eslint/typescript-estree": "1.7.0", + "@typescript-eslint/experimental-utils": "1.7.0", "eslint-utils": "^1.3.1", "regexpp": "^2.0.1", "requireindex": "^1.2.0", diff --git a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts index 8e864c9c4a2..80c8b131587 100644 --- a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts +++ b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type RuleNode = diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 4aef0268fa1..62ace5d4317 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -2,7 +2,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree, -} from '@typescript-eslint/typescript-estree'; +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; /** diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index 3bee5b029f7..f9981920fab 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -1,5 +1,5 @@ import * as tsutils from 'tsutils'; -import * as ts from 'typescript'; +import ts from 'typescript'; import * as util from '../util'; @@ -26,9 +26,9 @@ export default util.createRule({ return { AwaitExpression(node) { - const originalNode = parserServices.esTreeNodeToTSNodeMap.get( - node, - ) as ts.AwaitExpression; + const originalNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.AwaitExpression + >(node); const type = checker.getTypeAtLocation(originalNode.expression); if ( diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 77756ad15a8..25c0a08f3ab 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -1,5 +1,8 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; -import { ReportFixFunction } from 'ts-eslint'; +import { + TSESLint, + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -94,7 +97,7 @@ export default util.createRule({ let customMessage = ''; const bannedCfgValue = bannedTypes[node.name]; - let fix: ReportFixFunction | null = null; + let fix: TSESLint.ReportFixFunction | null = null; if (typeof bannedCfgValue === 'string') { customMessage += ` ${bannedCfgValue}`; diff --git a/packages/eslint-plugin/src/rules/camelcase.ts b/packages/eslint-plugin/src/rules/camelcase.ts index ec7ba9ea4fe..f3cb5948e5d 100644 --- a/packages/eslint-plugin/src/rules/camelcase.ts +++ b/packages/eslint-plugin/src/rules/camelcase.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/camelcase'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/class-name-casing.ts b/packages/eslint-plugin/src/rules/class-name-casing.ts index 2e9a15ffde7..829f29f24b8 100644 --- a/packages/eslint-plugin/src/rules/class-name-casing.ts +++ b/packages/eslint-plugin/src/rules/class-name-casing.ts @@ -1,5 +1,8 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; export default util.createRule({ name: 'class-name-casing', diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index 6228f014311..914e4172c79 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -1,11 +1,13 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ { allowExpressions?: boolean; allowTypedFunctionExpressions?: boolean; - allowUntypedSetters?: boolean; } ]; type MessageIds = 'missingReturnType'; @@ -42,7 +44,6 @@ export default util.createRule({ { allowExpressions: false, allowTypedFunctionExpressions: false, - allowUntypedSetters: true, }, ], create(context, [options]) { diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index be79d684a69..d760f18ea94 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type AccessibilityLevel = diff --git a/packages/eslint-plugin/src/rules/func-call-spacing.ts b/packages/eslint-plugin/src/rules/func-call-spacing.ts index 63f9d05e19e..fd395887480 100644 --- a/packages/eslint-plugin/src/rules/func-call-spacing.ts +++ b/packages/eslint-plugin/src/rules/func-call-spacing.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { isOpeningParenToken } from 'eslint-utils'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts index 002fbd3d8ee..e530efb4099 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/BinarySearchTree.ts @@ -1,8 +1,8 @@ // The following code is adapted from the the code in eslint. // License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import createTree = require('functional-red-black-tree'); +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import createTree from 'functional-red-black-tree'; export type TokenOrComment = TSESTree.Token | TSESTree.Comment; export interface TreeValue { diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts index 1ab3dd71ebb..deacc272bb3 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/OffsetStorage.ts @@ -1,9 +1,9 @@ // The following code is adapted from the the code in eslint. // License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE -import { TokenInfo } from './TokenInfo'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { BinarySearchTree, TokenOrComment } from './BinarySearchTree'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TokenInfo } from './TokenInfo'; /** * A class to store information on desired offsets of tokens from each other diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts index 29aaecdaa6d..9b7d345fe3d 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/TokenInfo.ts @@ -1,18 +1,17 @@ // The following code is adapted from the the code in eslint. // License: https://github.com/eslint/eslint/blob/48700fc8408f394887cdedd071b22b757700fdcb/LICENSE -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import { SourceCode } from 'ts-eslint'; +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import { TokenOrComment } from './BinarySearchTree'; /** * A helper class to get token-based info related to indentation */ export class TokenInfo { - private sourceCode: SourceCode; + private sourceCode: TSESLint.SourceCode; public firstTokensByLineNumber: Map; - constructor(sourceCode: SourceCode) { + constructor(sourceCode: TSESLint.SourceCode) { this.sourceCode = sourceCode; this.firstTokensByLineNumber = sourceCode.tokensAndComments.reduce( (map, token) => { diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts index f9a3ebcfb49..1944fdf2bf5 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -6,7 +6,8 @@ import { AST_NODE_TYPES, TSESTree, AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; + TSESLint, +} from '@typescript-eslint/experimental-utils'; import { createGlobalLinebreakMatcher } from 'eslint/lib/util/ast-utils'; import { isOpeningParenToken, @@ -20,7 +21,6 @@ import { isColonToken, isCommentToken, } from 'eslint-utils'; -import { RuleListener, RuleFunction } from 'ts-eslint'; import { TokenOrComment } from './BinarySearchTree'; import { OffsetStorage } from './OffsetStorage'; import { TokenInfo } from './TokenInfo'; @@ -848,7 +848,7 @@ export default createRule({ const ignoredNodeFirstTokens = new Set(); - const baseOffsetListeners: RuleListener = { + const baseOffsetListeners: TSESLint.RuleListener = { 'ArrayExpression, ArrayPattern'( node: TSESTree.ArrayExpression | TSESTree.ArrayPattern, ) { @@ -1547,7 +1547,7 @@ export default createRule({ }; const listenerCallQueue: { - listener: RuleFunction; + listener: TSESLint.RuleFunction; node: TSESTree.Node; }[] = []; @@ -1558,7 +1558,7 @@ export default createRule({ * 3. Call `ignoreNode` on the node sometime after exiting it and before validating offsets. */ const offsetListeners = Object.keys(baseOffsetListeners).reduce< - RuleListener + TSESLint.RuleListener >( /* * Offset listener calls are deferred until traversal is finished, and are called as @@ -1577,7 +1577,7 @@ export default createRule({ * ignored nodes are known. */ (acc, key) => { - const listener = baseOffsetListeners[key] as RuleFunction< + const listener = baseOffsetListeners[key] as TSESLint.RuleFunction< TSESTree.Node >; acc[key] = node => listenerCallQueue.push({ listener, node }); diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts index 85187a7046b..6a7a5c31e13 100644 --- a/packages/eslint-plugin/src/rules/indent.ts +++ b/packages/eslint-plugin/src/rules/indent.ts @@ -4,7 +4,10 @@ * This is done intentionally based on the internal implementation of the base indent rule. */ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/indent'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/member-delimiter-style.ts b/packages/eslint-plugin/src/rules/member-delimiter-style.ts index bfa2ca94439..326a0d961e5 100644 --- a/packages/eslint-plugin/src/rules/member-delimiter-style.ts +++ b/packages/eslint-plugin/src/rules/member-delimiter-style.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Delimiter = 'comma' | 'none' | 'semi'; diff --git a/packages/eslint-plugin/src/rules/member-naming.ts b/packages/eslint-plugin/src/rules/member-naming.ts index c0252331f79..7c221360c0c 100644 --- a/packages/eslint-plugin/src/rules/member-naming.ts +++ b/packages/eslint-plugin/src/rules/member-naming.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; interface Config { diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index 6811cf5a2e4..ffcc6985073 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type MessageIds = 'incorrectOrder'; diff --git a/packages/eslint-plugin/src/rules/no-array-constructor.ts b/packages/eslint-plugin/src/rules/no-array-constructor.ts index d6c92491be1..5de11364ab8 100644 --- a/packages/eslint-plugin/src/rules/no-array-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-array-constructor.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index 78065969afb..7dd71ad583b 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -1,5 +1,8 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; -import { RuleListener } from 'ts-eslint'; +import { + AST_NODE_TYPES, + TSESTree, + TSESLint, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-extra-parens'; import * as util from '../util'; @@ -85,7 +88,7 @@ export default util.createRule({ return rule(node); } - const overrides: RuleListener = { + const overrides: TSESLint.RuleListener = { // ArrayExpression ArrowFunctionExpression(node) { if (node.body.type !== AST_NODE_TYPES.TSAsExpression) { diff --git a/packages/eslint-plugin/src/rules/no-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index 461b8c6efa8..b0917b5ae72 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index 9ee66b44564..3546cd1112d 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts index 049aa2b35b8..49689213992 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -3,7 +3,10 @@ * @author Scott O'Hara */ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-magic-numbers'; import * as util from '../util'; import { JSONSchema4 } from 'json-schema'; diff --git a/packages/eslint-plugin/src/rules/no-misused-new.ts b/packages/eslint-plugin/src/rules/no-misused-new.ts index 5730475cd0d..56c5eb5f296 100644 --- a/packages/eslint-plugin/src/rules/no-misused-new.ts +++ b/packages/eslint-plugin/src/rules/no-misused-new.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/no-namespace.ts b/packages/eslint-plugin/src/rules/no-namespace.ts index ad6eaf61107..2ab66102dd4 100644 --- a/packages/eslint-plugin/src/rules/no-namespace.ts +++ b/packages/eslint-plugin/src/rules/no-namespace.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ diff --git a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts index d8362013321..d946c879257 100644 --- a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ diff --git a/packages/eslint-plugin/src/rules/no-parameter-properties.ts b/packages/eslint-plugin/src/rules/no-parameter-properties.ts index 0d92c855aa8..6d1ffa80870 100644 --- a/packages/eslint-plugin/src/rules/no-parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/no-parameter-properties.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Modifier = diff --git a/packages/eslint-plugin/src/rules/no-require-imports.ts b/packages/eslint-plugin/src/rules/no-require-imports.ts index 98039a91b7a..69b4887c925 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/no-this-alias.ts b/packages/eslint-plugin/src/rules/no-this-alias.ts index e98c9c39659..30201acf6a0 100644 --- a/packages/eslint-plugin/src/rules/no-this-alias.ts +++ b/packages/eslint-plugin/src/rules/no-this-alias.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index 890e6b04aa5..b4a4274c274 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -1,5 +1,8 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; -import { ReportDescriptor } from 'ts-eslint'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -194,7 +197,7 @@ export default util.createRule({ compositionType: string | undefined, isRoot: boolean, type?: string, - ): ReportDescriptor { + ): TSESLint.ReportDescriptor { if (isRoot) { return { node, diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index d11b2527190..f382e7262e0 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import ts from 'typescript'; import * as tsutils from 'tsutils'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 1e78c75338d..c98b619eab4 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { isCallExpression, isNewExpression, diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts index ede16241eed..9fc32f6fc1c 100644 --- a/packages/eslint-plugin/src/rules/no-unused-vars.ts +++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts @@ -1,4 +1,7 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-unused-vars'; import * as util from '../util'; 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 085bb02975d..c18e8e0cc11 100644 --- a/packages/eslint-plugin/src/rules/no-use-before-define.ts +++ b/packages/eslint-plugin/src/rules/no-use-before-define.ts @@ -1,5 +1,8 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; -import { Scope } from 'ts-eslint'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; const SENTINEL_TYPE = /^(?:(?:Function|Class)(?:Declaration|Expression)|ArrowFunctionExpression|CatchClause|ImportDeclaration|ExportNamedDeclaration)$/; @@ -28,14 +31,14 @@ function parseOptions(options: string | Config | null): Required { /** * Checks whether or not a given scope is a top level scope. */ -function isTopLevelScope(scope: Scope.Scope): boolean { +function isTopLevelScope(scope: TSESLint.Scope.Scope): boolean { return scope.type === 'module' || scope.type === 'global'; } /** * Checks whether or not a given variable is a function declaration. */ -function isFunction(variable: Scope.Variable): boolean { +function isFunction(variable: TSESLint.Scope.Variable): boolean { return variable.defs[0].type === 'FunctionName'; } @@ -43,8 +46,8 @@ function isFunction(variable: Scope.Variable): boolean { * Checks whether or not a given variable is a class declaration in an upper function scope. */ function isOuterClass( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (variable.defs[0].type !== 'ClassName') { return false; @@ -64,8 +67,8 @@ function isOuterClass( * Checks whether or not a given variable is a variable declaration in an upper function scope. */ function isOuterVariable( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (variable.defs[0].type !== 'Variable') { return false; @@ -102,8 +105,8 @@ function isInRange( * - for (var a of a) {} */ function isInInitializer( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (variable.scope !== reference.from) { return false; @@ -199,8 +202,8 @@ export default util.createRule({ * @param reference The reference to the variable */ function isForbidden( - variable: Scope.Variable, - reference: Scope.Reference, + variable: TSESLint.Scope.Variable, + reference: TSESLint.Scope.Reference, ): boolean { if (isFunction(variable)) { return !!options.functions; @@ -217,7 +220,7 @@ export default util.createRule({ /** * Finds and validates all variables in a given scope. */ - function findVariablesInScope(scope: Scope.Scope): void { + function findVariablesInScope(scope: TSESLint.Scope.Scope): void { scope.references.forEach(reference => { const variable = reference.resolved; diff --git a/packages/eslint-plugin/src/rules/no-useless-constructor.ts b/packages/eslint-plugin/src/rules/no-useless-constructor.ts index dcac02df846..e6b48a055ed 100644 --- a/packages/eslint-plugin/src/rules/no-useless-constructor.ts +++ b/packages/eslint-plugin/src/rules/no-useless-constructor.ts @@ -1,4 +1,7 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-useless-constructor'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/no-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index 67b61d13175..a8acccd8af9 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = []; diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts index 4b542cd9109..8e829b48e9c 100644 --- a/packages/eslint-plugin/src/rules/prefer-for-of.ts +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -1,6 +1,9 @@ -import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { Scope } from 'ts-eslint'; export default util.createRule({ name: 'prefer-for-of', @@ -159,7 +162,7 @@ export default util.createRule({ function isIndexOnlyUsedWithArray( body: TSESTree.Statement, - indexVar: Scope.Variable, + indexVar: TSESLint.Scope.Variable, arrayExpression: TSESTree.Expression, ): boolean { const sourceCode = context.getSourceCode(); diff --git a/packages/eslint-plugin/src/rules/prefer-function-type.ts b/packages/eslint-plugin/src/rules/prefer-function-type.ts index e7a95d705e9..95f1a8ee3ca 100644 --- a/packages/eslint-plugin/src/rules/prefer-function-type.ts +++ b/packages/eslint-plugin/src/rules/prefer-function-type.ts @@ -1,8 +1,8 @@ import { AST_NODE_TYPES, - TSESTree, AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index f7b3eb153ee..2db17e11d1d 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { getStaticValue } from 'eslint-utils'; import { AST as RegExpAST, parseRegExpLiteral } from 'regexpp'; import ts from 'typescript'; diff --git a/packages/eslint-plugin/src/rules/prefer-interface.ts b/packages/eslint-plugin/src/rules/prefer-interface.ts index 10308d7e70e..6efbf62d6a6 100644 --- a/packages/eslint-plugin/src/rules/prefer-interface.ts +++ b/packages/eslint-plugin/src/rules/prefer-interface.ts @@ -1,5 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import { RuleFix } from 'ts-eslint'; +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ @@ -33,7 +32,7 @@ export default util.createRule({ messageId: 'interfaceOverType', fix(fixer) { const typeNode = node.typeParameters || node.id; - const fixes: RuleFix[] = []; + const fixes: TSESLint.RuleFix[] = []; const firstToken = sourceCode.getFirstToken(node); if (firstToken) { diff --git a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts index 37eced4e545..9d60e0eed4f 100644 --- a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts +++ b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts @@ -1,7 +1,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 6b3079cdfdf..565a4cb83db 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -1,11 +1,10 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import { isNotClosingParenToken, getPropertyName, getStaticValue, } from 'eslint-utils'; import { RegExpParser, AST as RegExpAST } from 'regexpp'; -import { RuleFixer, RuleFix } from 'ts-eslint'; import ts from 'typescript'; import { createRule, getParserServices } from '../util'; @@ -314,11 +313,11 @@ export default createRule({ * @param negative The flag to fix to negative condition. */ function* fixWithRightOperand( - fixer: RuleFixer, + fixer: TSESLint.RuleFixer, node: TSESTree.BinaryExpression, kind: 'start' | 'end', negative: boolean, - ): IterableIterator { + ): IterableIterator { // left is CallExpression or MemberExpression. const leftNode = (node.left.type === 'CallExpression' ? node.left.callee @@ -344,11 +343,11 @@ export default createRule({ * @param negative The flag to fix to negative condition. */ function* fixWithArgument( - fixer: RuleFixer, + fixer: TSESLint.RuleFixer, node: TSESTree.BinaryExpression, kind: 'start' | 'end', negative: boolean, - ): IterableIterator { + ): IterableIterator { const callNode = node.left as TSESTree.CallExpression; const calleeNode = callNode.callee as TSESTree.MemberExpression; diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 75d2ccdacc7..0eb96e99143 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ diff --git a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts index 8699c0cf073..a9745be16e0 100644 --- a/packages/eslint-plugin/src/rules/require-array-sort-compare.ts +++ b/packages/eslint-plugin/src/rules/require-array-sort-compare.ts @@ -1,5 +1,5 @@ -import * as ts from 'typescript'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; +import ts from 'typescript'; import * as util from '../util'; export default util.createRule({ diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index ef73ae7478d..9a3a42c8f32 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -1,4 +1,4 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import ts from 'typescript'; import * as util from '../util'; diff --git a/packages/eslint-plugin/src/rules/semi.ts b/packages/eslint-plugin/src/rules/semi.ts index 89fe6180333..6e40651dd2e 100644 --- a/packages/eslint-plugin/src/rules/semi.ts +++ b/packages/eslint-plugin/src/rules/semi.ts @@ -1,6 +1,9 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + TSESTree, + TSESLint, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/semi'; -import { RuleListener, RuleFunction } from 'ts-eslint'; import * as util from '../util'; export type Options = util.InferOptionsTypeFromRule; @@ -28,7 +31,7 @@ export default util.createRule({ ], create(context) { const rules = baseRule.create(context); - const checkForSemicolon = rules.ExpressionStatement as RuleFunction< + const checkForSemicolon = rules.ExpressionStatement as TSESLint.RuleFunction< TSESTree.Node >; @@ -48,7 +51,7 @@ export default util.createRule({ AST_NODE_TYPES.TSExportAssignment, AST_NODE_TYPES.TSImportEqualsDeclaration, AST_NODE_TYPES.TSTypeAliasDeclaration, - ].reduce((acc, node) => { + ].reduce((acc, node) => { acc[node] = checkForSemicolon; return acc; }, {}); diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts index 9deac5cd977..19ef206119f 100644 --- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts +++ b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts @@ -1,5 +1,5 @@ +import { TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; type Options = [ { diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index 6b177f1dccf..cd273a65456 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -1,7 +1,9 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as tsutils from 'tsutils'; -import * as ts from 'typescript'; - +import ts from 'typescript'; import * as util from '../util'; //------------------------------------------------------------------------------ diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index c1c1ad470da..d4ea472be5e 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -1,5 +1,8 @@ +import { + AST_NODE_TYPES, + TSESTree, +} from '@typescript-eslint/experimental-utils'; import * as util from '../util'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; interface Failure { unify: Unify; diff --git a/packages/eslint-plugin/src/util/createRule.ts b/packages/eslint-plugin/src/util/createRule.ts index ac61c39cb55..5982f04c3d5 100644 --- a/packages/eslint-plugin/src/util/createRule.ts +++ b/packages/eslint-plugin/src/util/createRule.ts @@ -1,62 +1,9 @@ -import RuleModule, { - RuleListener, - RuleMetaData, - RuleMetaDataDocs, - RuleContext, -} from 'ts-eslint'; -import { applyDefault } from './applyDefault'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; // note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder const version = require('../../package.json').version; -// Utility type to remove a list of properties from an object -type RemoveProps< - TObj extends Record, - TKeys extends keyof TObj -> = Pick>; - -// we'll automatically add the url + tslint description for people. -type CreateRuleMetaDocs = RemoveProps & { - tslintName?: string; -}; -type CreateRuleMeta = { - docs: CreateRuleMetaDocs; -} & RemoveProps, 'docs'>; - -// This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 -// TODO - when the above rule lands; add type checking for the context.report `data` property -export function createRule< - TOptions extends any[], - TMessageIds extends string, - TRuleListener extends RuleListener = RuleListener ->({ - name, - meta, - defaultOptions, - create, -}: { - name: string; - meta: CreateRuleMeta; - defaultOptions: TOptions; - create: ( - context: RuleContext, - optionsWithDefault: TOptions, - ) => TRuleListener; -}): RuleModule { - return { - meta: { - ...meta, - docs: { - ...meta.docs, - url: `https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin/docs/rules/${name}.md`, - extraDescription: meta.docs.tslintName - ? [`\`${meta.docs.tslintName}\` from TSLint`] - : undefined, - }, - }, - create(context) { - const optionsWithDefault = applyDefault(defaultOptions, context.options); - return create(context, optionsWithDefault); - }, - }; -} +export const createRule = ESLintUtils.RuleCreator( + name => + `https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin/docs/rules/${name}.md`, +); diff --git a/packages/eslint-plugin/src/util/getParserServices.ts b/packages/eslint-plugin/src/util/getParserServices.ts index a63297708cc..2cc8b498159 100644 --- a/packages/eslint-plugin/src/util/getParserServices.ts +++ b/packages/eslint-plugin/src/util/getParserServices.ts @@ -1,5 +1,5 @@ import { ParserServices } from '@typescript-eslint/parser'; -import { RuleContext } from 'ts-eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; type RequiredParserServices = { [k in keyof ParserServices]: Exclude @@ -11,7 +11,9 @@ type RequiredParserServices = { export function getParserServices< TMessageIds extends string, TOptions extends any[] ->(context: RuleContext): RequiredParserServices { +>( + context: TSESLint.RuleContext, +): RequiredParserServices { if ( !context.parserServices || !context.parserServices.program || diff --git a/packages/eslint-plugin/src/util/index.ts b/packages/eslint-plugin/src/util/index.ts index d2291cded47..b1aae71b357 100644 --- a/packages/eslint-plugin/src/util/index.ts +++ b/packages/eslint-plugin/src/util/index.ts @@ -1,7 +1,11 @@ -export * from './applyDefault'; +import { ESLintUtils } from '@typescript-eslint/experimental-utils'; + export * from './astUtils'; export * from './createRule'; -export * from './deepMerge'; export * from './getParserServices'; export * from './misc'; export * from './types'; + +// this is done for convenience - saves migrating all of the old rules +const { applyDefault, deepMerge, isObjectNotArray } = ESLintUtils; +export { applyDefault, deepMerge, isObjectNotArray }; diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index 07b6a0b3c3d..2a9ba2a1c93 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -2,9 +2,11 @@ * @fileoverview Really small utility functions that didn't deserve their own files */ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; -import RuleModule from 'ts-eslint'; -import { SourceCode } from 'ts-eslint'; +import { + AST_NODE_TYPES, + TSESLint, + TSESTree, +} from '@typescript-eslint/experimental-utils'; /** * Check if the context file name is *.ts or *.tsx @@ -27,7 +29,7 @@ export function upperCaseFirst(str: string) { return str[0].toUpperCase() + str.slice(1); } -type InferOptionsTypeFromRuleNever = T extends RuleModule< +type InferOptionsTypeFromRuleNever = T extends TSESLint.RuleModule< never, infer TOptions > @@ -36,7 +38,7 @@ type InferOptionsTypeFromRuleNever = T extends RuleModule< /** * Uses type inference to fetch the TOptions type from the given RuleModule */ -export type InferOptionsTypeFromRule = T extends RuleModule< +export type InferOptionsTypeFromRule = T extends TSESLint.RuleModule< any, infer TOptions > @@ -46,7 +48,7 @@ export type InferOptionsTypeFromRule = T extends RuleModule< /** * Uses type inference to fetch the TMessageIds type from the given RuleModule */ -export type InferMessageIdsTypeFromRule = T extends RuleModule< +export type InferMessageIdsTypeFromRule = T extends TSESLint.RuleModule< infer TMessageIds, any > @@ -88,7 +90,7 @@ export function arraysAreEqual( */ export function getNameFromClassMember( methodDefinition: TSESTree.MethodDefinition | TSESTree.ClassProperty, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, ): string { if (keyCanBeReadAsPropertyName(methodDefinition.key)) { return getNameFromPropertyName(methodDefinition.key); diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index 2703d00b5c2..4db8bf3909c 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,131 +1,13 @@ -import { ParserOptions } from '@typescript-eslint/parser'; -import { - AST_NODE_TYPES, - AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; +import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; import { RuleTester as ESLintRuleTester } from 'eslint'; import * as path from 'path'; -import RuleModule from 'ts-eslint'; -interface ValidTestCase> { - code: string; - options?: TOptions; - filename?: string; - parserOptions?: ParserOptions; - settings?: Record; - parser?: string; - globals?: Record; - env?: { - browser?: boolean; - }; -} - -interface InvalidTestCase< - TMessageIds extends string, - TOptions extends Readonly -> extends ValidTestCase { - errors: TestCaseError[]; - output?: string | null; -} - -interface TestCaseError { - messageId: TMessageIds; - data?: Record; - type?: AST_NODE_TYPES | AST_TOKEN_TYPES; - line?: number; - column?: number; -} - -interface RunTests< - TMessageIds extends string, - TOptions extends Readonly -> { - // RuleTester.run also accepts strings for valid cases - valid: (ValidTestCase | string)[]; - invalid: InvalidTestCase[]; -} - -declare class RuleTesterTyped { - run>( - name: string, - rule: RuleModule, - tests: RunTests, - ): void; -} - -const RuleTester = (ESLintRuleTester as any) as { - new (config?: { - parser: '@typescript-eslint/parser'; - parserOptions?: ParserOptions; - }): RuleTesterTyped; -}; +const RuleTester: TSESLint.RuleTester = ESLintRuleTester as any; function getFixturesRootDir() { return path.join(process.cwd(), 'tests/fixtures/'); } -/** - * Converts a batch of single line tests into a number of separate test cases. - * This makes it easier to write tests which use the same options. - * - * Why wouldn't you just leave them as one test? - * Because it makes the test error messages harder to decipher. - * This way each line will fail separately, instead of them all failing together. - */ -function batchedSingleLineTests>( - test: ValidTestCase, -): ValidTestCase[]; -/** - * Converts a batch of single line tests into a number of separate test cases. - * This makes it easier to write tests which use the same options. - * - * Why wouldn't you just leave them as one test? - * Because it makes the test error messages harder to decipher. - * This way each line will fail separately, instead of them all failing together. - * - * Make sure you have your line numbers correct for error reporting, as it will match - * the line numbers up with the split tests! - */ -function batchedSingleLineTests< - TMessageIds extends string, - TOptions extends Readonly ->( - test: InvalidTestCase, -): InvalidTestCase[]; -function batchedSingleLineTests< - TMessageIds extends string, - TOptions extends Readonly ->( - options: ValidTestCase | InvalidTestCase, -): (ValidTestCase | InvalidTestCase)[] { - // eslint counts lines from 1 - const lineOffset = options.code[0] === '\n' ? 2 : 1; - return options.code - .trim() - .split('\n') - .map((code, i) => { - const lineNum = i + lineOffset; - const errors = - 'errors' in options - ? options.errors.filter(e => e.line === lineNum) - : []; - return { - ...options, - code, - errors: errors.map(e => ({ - ...e, - line: 1, - })), - }; - }); -} +const { batchedSingleLineTests } = ESLintUtils; -export { - RuleTester, - RunTests, - TestCaseError, - InvalidTestCase, - ValidTestCase, - batchedSingleLineTests, - getFixturesRootDir, -}; +export { RuleTester, getFixturesRootDir, batchedSingleLineTests }; 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 6baa018ebf6..1a1fb94a462 100644 --- a/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts +++ b/packages/eslint-plugin/tests/eslint-rules/no-redeclare.test.ts @@ -1,5 +1,5 @@ import rule from 'eslint/lib/rules/no-redeclare'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import { RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ diff --git a/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts b/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts index 580f2a57d30..d0b8afe9b19 100644 --- a/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/func-call-spacing.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule, { MessageIds, Options } from '../../src/rules/func-call-spacing'; -import { RuleTester, ValidTestCase, InvalidTestCase } from '../RuleTester'; +import { RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -32,7 +33,7 @@ ruleTester.run('func-call-spacing', rule, { '( f )( 0 )', '( (f) )( (0) )', '( f()() )(0)', - ].map>(code => ({ + ].map>(code => ({ code, options: ['never'], })), @@ -59,7 +60,7 @@ ruleTester.run('func-call-spacing', rule, { '( f ) ( 0 )', '( (f) ) ( (0) )', '( f () ) (0)', - ].map>(code => ({ + ].map>(code => ({ code, options: ['always'], })), @@ -75,7 +76,7 @@ ruleTester.run('func-call-spacing', rule, { 'f\u2028();', 'f\u2029();', 'f\r\n();', - ].map>(code => ({ + ].map>(code => ({ code, options: ['always', { allowNewlines: true }], })), @@ -191,7 +192,7 @@ var a = foo code: 'f\r\n();', output: null, // no change }, - ].map>( + ].map>( code => ({ options: ['never'], @@ -226,7 +227,7 @@ var a = foo code: 'f(0) (1)', output: 'f (0) (1)', }, - ].map>( + ].map>( code => ({ options: ['always'], @@ -302,7 +303,7 @@ var a = foo code: 'f\r\n();', output: 'f ();', }, - ].map>( + ].map>( code => ({ options: ['always'], @@ -355,7 +356,7 @@ var a = foo output: 'f ();\n t ();', errors: [{ messageId: 'missing' }, { messageId: 'missing' }], }, - ].map>( + ].map>( code => ({ options: ['always', { allowNewlines: true }], diff --git a/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts index ea44a421249..e0515e869c9 100644 --- a/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent-eslint.test.ts @@ -7,7 +7,7 @@ import { AST_TOKEN_TYPES, AST_NODE_TYPES, -} from '@typescript-eslint/typescript-estree'; +} from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import path from 'path'; import rule from '../../../src/rules/indent-new-do-not-use'; diff --git a/packages/eslint-plugin/tests/rules/indent/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts index 647cbf753fc..e992b440c6c 100644 --- a/packages/eslint-plugin/tests/rules/indent/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { RuleTester } from '../../RuleTester'; import rule from '../../../src/rules/indent'; -import { RuleTester, RunTests, TestCaseError } from '../../RuleTester'; import { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, @@ -608,7 +609,7 @@ type Foo = string | { `, ], }, -].reduce>( +].reduce>( (acc, testCase) => { const indent = ' '; @@ -630,7 +631,7 @@ type Foo = string | { output: code, errors: code .split('\n') - .map | null>((line, lineNum) => { + .map | null>((line, lineNum) => { const indentCount = line.split(indent).length - 1; const spaceCount = indentCount * indent.length; @@ -649,7 +650,8 @@ type Foo = string | { }; }) .filter( - (error): error is TestCaseError => error !== null, + (error): error is TSESLint.TestCaseError => + error !== null, ), }); }); diff --git a/packages/eslint-plugin/tests/rules/indent/utils.ts b/packages/eslint-plugin/tests/rules/indent/utils.ts index c4bf28c08fb..091f68740cd 100644 --- a/packages/eslint-plugin/tests/rules/indent/utils.ts +++ b/packages/eslint-plugin/tests/rules/indent/utils.ts @@ -4,8 +4,8 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES, -} from '@typescript-eslint/typescript-estree'; -import { TestCaseError } from '../../RuleTester'; + TSESLint, +} from '@typescript-eslint/experimental-utils'; import rule from '../../../src/rules/indent'; import { InferMessageIdsTypeFromRule } from '../../../src/util'; @@ -54,7 +54,7 @@ function is2DProvidedErrorArr( */ export function expectedErrors( providedErrors: ProvidedError | ProvidedError[], -): TestCaseError[]; +): TSESLint.TestCaseError[]; /** * Create error message object for failure cases with a single 'found' indentation type * @param providedIndentType indent type of string or tab @@ -64,11 +64,11 @@ export function expectedErrors( export function expectedErrors( providedIndentType: string, providedErrors: ProvidedError | ProvidedError[], -): TestCaseError[]; +): TSESLint.TestCaseError[]; export function expectedErrors( providedIndentType: string | ProvidedError | ProvidedError[], providedErrors?: ProvidedError | ProvidedError[], -): TestCaseError[] { +): TSESLint.TestCaseError[] { let indentType: string; let errors: ProvidedError[]; diff --git a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts index 5e920dd088f..6b8570bb4a4 100644 --- a/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts +++ b/packages/eslint-plugin/tests/rules/no-array-constructor.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-array-constructor'; import { RuleTester } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts b/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts index 0bee5ac6324..1d2742ed040 100644 --- a/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extraneous-class.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-extraneous-class'; import { RuleTester } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts b/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts index ad2395a93d0..44a40942c9f 100644 --- a/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts +++ b/packages/eslint-plugin/tests/rules/no-for-in-array.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-for-in-array'; import { RuleTester, getFixturesRootDir } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts index 007fe8042d9..fb2563ed4ee 100644 --- a/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts +++ b/packages/eslint-plugin/tests/rules/no-inferrable-types.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-inferrable-types'; -import { RuleTester, InvalidTestCase } from '../RuleTester'; +import { RuleTester } from '../RuleTester'; import { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, @@ -61,7 +62,10 @@ const testCases = [ const validTestCases = flatten( testCases.map(c => c.code.map(code => `const a = ${code}`)), ); -const invalidTestCases: InvalidTestCase[] = flatten( +const invalidTestCases: TSESLint.InvalidTestCase< + MessageIds, + Options +>[] = flatten( testCases.map(cas => cas.code.map(code => ({ code: `const a: ${cas.type} = ${code}`, diff --git a/packages/eslint-plugin/tests/rules/no-this-alias.test.ts b/packages/eslint-plugin/tests/rules/no-this-alias.test.ts index ea0320ac343..c1ddce12186 100644 --- a/packages/eslint-plugin/tests/rules/no-this-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-this-alias.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/no-this-alias'; import { RuleTester } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts index 6c2a18dacc8..53a349e552e 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-qualifier.test.ts @@ -1,7 +1,7 @@ import path from 'path'; import rule from '../../src/rules/no-unnecessary-qualifier'; import { RuleTester } from '../RuleTester'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; const messageId = 'unnecessaryQualifier'; const rootPath = path.join(process.cwd(), 'tests/fixtures/'); diff --git a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts index e1bca791b33..7a0324308b2 100644 --- a/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts +++ b/packages/eslint-plugin/tests/rules/no-use-before-define.test.ts @@ -1,6 +1,6 @@ import rule from '../../src/rules/no-use-before-define'; import { RuleTester } from '../RuleTester'; -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', diff --git a/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts b/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts index 5743bcc7f4d..a472d8619fe 100644 --- a/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts @@ -1,4 +1,4 @@ -import { AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/prefer-function-type'; import { RuleTester } from '../RuleTester'; diff --git a/packages/eslint-plugin/tests/rules/semi.test.ts b/packages/eslint-plugin/tests/rules/semi.test.ts index c4569c79ba0..83bc750a235 100644 --- a/packages/eslint-plugin/tests/rules/semi.test.ts +++ b/packages/eslint-plugin/tests/rules/semi.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule, { MessageIds, Options } from '../../src/rules/semi'; -import { InvalidTestCase, RuleTester, ValidTestCase } from '../RuleTester'; +import { RuleTester } from '../RuleTester'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -87,7 +88,7 @@ class PanCamera extends FreeCamera { 'export default (foo) => foo.bar();', 'export default foo = 42;', 'export default foo += 42;', - ].reduce[]>( + ].reduce[]>( (acc, code) => { acc.push({ code, @@ -616,7 +617,7 @@ class PanCamera extends FreeCamera { }, ], }, - ].reduce[]>( + ].reduce[]>( (acc, test) => { acc.push({ code: test.code.replace(/;/g, ''), diff --git a/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts b/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts index 05f75faf408..6559c9b7140 100644 --- a/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts +++ b/packages/eslint-plugin/tests/rules/type-annotation-spacing.test.ts @@ -1,5 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { RuleTester } from '../RuleTester'; import rule from '../../src/rules/type-annotation-spacing'; -import { RuleTester, InvalidTestCase, ValidTestCase } from '../RuleTester'; import { InferMessageIdsTypeFromRule, InferOptionsTypeFromRule, @@ -6317,7 +6318,7 @@ type Foo = { const operators = ['+?:', '-?:']; ruleTester.run('type-annotation-spacing', rule, { - valid: operators.reduce[]>( + valid: operators.reduce[]>( (validCases, operator) => validCases.concat([ { @@ -6359,7 +6360,7 @@ ruleTester.run('type-annotation-spacing', rule, { ]), [], ), - invalid: operators.reduce[]>( + invalid: operators.reduce[]>( (invalidCases, operator) => invalidCases.concat([ // space before + after cases diff --git a/packages/eslint-plugin/tests/util.test.ts b/packages/eslint-plugin/tests/util.test.ts index 957b50b286e..59c820d28b9 100644 --- a/packages/eslint-plugin/tests/util.test.ts +++ b/packages/eslint-plugin/tests/util.test.ts @@ -69,118 +69,6 @@ describe('isDefinitionFile', () => { }); }); -describe('deepMerge', () => { - it('creates a brand new object', () => { - const a = {}; - const b = {}; - const result = util.deepMerge(a, b); - - assert.notStrictEqual(result, a); - assert.notStrictEqual(result, b); - }); - - it('deeply merges objects', () => { - const a = { - stringA1: 'asdf', - numberA1: 1, - boolA1: true, - arrayA1: [1, 2, 3], - objA1: { - stringA2: 'fsda', - numberA2: 2, - boolA2: false, - arrayA2: [3, 2, 1], - objA2: {}, - }, - }; - const b = { - stringB1: 'asdf', - numberB1: 1, - boolB1: true, - arrayB1: [1, 2, 3], - objB1: { - stringB2: 'fsda', - numberB2: 2, - boolB2: false, - arrayB2: [3, 2, 1], - objB2: {}, - }, - }; - - assert.deepStrictEqual(util.deepMerge(a, b), Object.assign({}, a, b)); - }); - - it('deeply overwrites properties in the first one with the second', () => { - const a = { - prop1: { - prop2: 'hi', - }, - }; - const b = { - prop1: { - prop2: 'bye', - }, - }; - - assert.deepStrictEqual(util.deepMerge(a, b), b); - }); -}); - -describe('applyDefault', () => { - it('returns a clone of the default if no options given', () => { - const defaults = [ - { - prop: 'setting', - }, - ]; - const user = null; - const result = util.applyDefault(defaults, user); - - assert.deepStrictEqual(result, defaults); - assert.notStrictEqual(result, defaults); - }); - - it('returns applies a deepMerge to each element in the array', () => { - const defaults = [ - { - prop: 'setting1', - other: 'other', - }, - { - prop: 'setting2', - }, - ] as Record[]; - const user = [ - { - prop: 'new', - other: 'something', - }, - ] as Record[]; - const result = util.applyDefault(defaults, user); - - assert.deepStrictEqual(result, [ - { - prop: 'new', - other: 'something', - }, - { - prop: 'setting2', - }, - ]); - assert.notStrictEqual(result, defaults); - assert.notStrictEqual(result, user); - }); - - it('returns a brand new array', () => { - const defaults: undefined[] = []; - const user: undefined[] = []; - const result = util.applyDefault(defaults, user); - - assert.notStrictEqual(result, defaults); - assert.notStrictEqual(result, user); - }); -}); - describe('upperCaseFirst', () => { it('upper cases first', () => { assert.strictEqual(util.upperCaseFirst('hello'), 'Hello'); diff --git a/packages/eslint-plugin/typings/eslint-rules.d.ts b/packages/eslint-plugin/typings/eslint-rules.d.ts index 7ea46da7c0b..98ee285d1a2 100644 --- a/packages/eslint-plugin/typings/eslint-rules.d.ts +++ b/packages/eslint-plugin/typings/eslint-rules.d.ts @@ -1,15 +1,13 @@ // don't provide a general import case so that people have to strictly type out a declaration -// declare module 'eslint/lib/rules/*' { -// import RuleModule from 'ts-eslint'; -// const rule: RuleModule; +// declare module 'eslint/lib/rules/*' TSESLint, { +// const rule: TSESLint.RuleModule; // export = rule; // } declare module 'eslint/lib/rules/arrow-parens' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< | 'unexpectedParens' | 'expectedParens' | 'unexpectedParensInline' @@ -28,10 +26,9 @@ declare module 'eslint/lib/rules/arrow-parens' { } declare module 'eslint/lib/rules/camelcase' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'notCamelCase', [ { @@ -48,12 +45,11 @@ declare module 'eslint/lib/rules/camelcase' { } declare module 'eslint/lib/rules/indent' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; type Listener = (node: TSESTree.Node) => void; type ElementList = number | 'first' | 'off'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'wrongIndentation', [ ('tab' | number)?, @@ -146,10 +142,9 @@ declare module 'eslint/lib/rules/indent' { } declare module 'eslint/lib/rules/no-dupe-args' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'unexpected', [], { @@ -161,10 +156,9 @@ declare module 'eslint/lib/rules/no-dupe-args' { } declare module 'eslint/lib/rules/no-implicit-globals' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [], { @@ -175,10 +169,9 @@ declare module 'eslint/lib/rules/no-implicit-globals' { } declare module 'eslint/lib/rules/no-magic-numbers' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'noMagic', [ { @@ -197,10 +190,9 @@ declare module 'eslint/lib/rules/no-magic-numbers' { } declare module 'eslint/lib/rules/no-redeclare' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [ { @@ -215,10 +207,9 @@ declare module 'eslint/lib/rules/no-redeclare' { } declare module 'eslint/lib/rules/no-restricted-globals' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, ( | string @@ -234,10 +225,9 @@ declare module 'eslint/lib/rules/no-restricted-globals' { } declare module 'eslint/lib/rules/no-shadow' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [ { @@ -254,10 +244,9 @@ declare module 'eslint/lib/rules/no-shadow' { } declare module 'eslint/lib/rules/no-undef' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'undef', [ { @@ -272,10 +261,9 @@ declare module 'eslint/lib/rules/no-undef' { } declare module 'eslint/lib/rules/no-unused-vars' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, ( | 'all' @@ -297,10 +285,9 @@ declare module 'eslint/lib/rules/no-unused-vars' { } declare module 'eslint/lib/rules/no-use-before-define' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, ( | 'nofunc' @@ -317,10 +304,9 @@ declare module 'eslint/lib/rules/no-use-before-define' { } declare module 'eslint/lib/rules/strict' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< | 'function' | 'global' | 'multiple' @@ -340,10 +326,9 @@ declare module 'eslint/lib/rules/strict' { } declare module 'eslint/lib/rules/no-useless-constructor' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [], { @@ -354,10 +339,9 @@ declare module 'eslint/lib/rules/no-useless-constructor' { } declare module 'eslint/lib/rules/no-extra-parens' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< 'unexpected', [ 'all' | 'functions', @@ -407,10 +391,9 @@ declare module 'eslint/lib/rules/no-extra-parens' { } declare module 'eslint/lib/rules/semi' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import RuleModule from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; - const rule: RuleModule< + const rule: TSESLint.RuleModule< never, [ 'always' | 'never', diff --git a/packages/eslint-plugin/typings/eslint-utils.d.ts b/packages/eslint-plugin/typings/eslint-utils.d.ts index d93c90532da..e7cefb09367 100644 --- a/packages/eslint-plugin/typings/eslint-utils.d.ts +++ b/packages/eslint-plugin/typings/eslint-utils.d.ts @@ -1,13 +1,12 @@ declare module 'eslint-utils' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import { Scope, SourceCode } from 'ts-eslint'; + import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; export function getFunctionHeadLocation( node: | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, ): TSESTree.SourceLocation; export function getFunctionNameWithKind( @@ -22,22 +21,22 @@ declare module 'eslint-utils' { | TSESTree.MemberExpression | TSESTree.Property | TSESTree.MethodDefinition, - initialScope?: Scope.Scope, + initialScope?: TSESLint.Scope.Scope, ): string | null; export function getStaticValue( node: TSESTree.Node, - initialScope?: Scope.Scope, + initialScope?: TSESLint.Scope.Scope, ): { value: any } | null; export function getStringIfConstant( node: TSESTree.Node, - initialScope?: Scope.Scope, + initialScope?: TSESLint.Scope.Scope, ): string | null; export function hasSideEffect( node: TSESTree.Node, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, options?: { considerGetters?: boolean; considerImplicitTypeConversion?: boolean; @@ -46,7 +45,7 @@ declare module 'eslint-utils' { export function isParenthesized( node: TSESTree.Node, - sourceCode: SourceCode, + sourceCode: TSESLint.SourceCode, ): boolean; export class PatternMatcher { @@ -56,14 +55,14 @@ declare module 'eslint-utils' { } export function findVariable( - initialScope: Scope.Scope, + initialScope: TSESLint.Scope.Scope, name: string, - ): Scope.Variable | null; + ): TSESLint.Scope.Variable | null; export function getInnermostScope( - initialScope: Scope.Scope, + initialScope: TSESLint.Scope.Scope, node: TSESTree.Node, - ): Scope.Scope; + ): TSESLint.Scope.Scope; export class ReferenceTracker { static readonly READ: unique symbol; @@ -71,7 +70,7 @@ declare module 'eslint-utils' { static readonly CONSTRUCT: unique symbol; constructor( - globalScope: Scope.Scope, + globalScope: TSESLint.Scope.Scope, options?: { mode: 'strict' | 'legacy'; globalObjectNames: readonly string[]; diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts deleted file mode 100644 index 62ef79199a2..00000000000 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ /dev/null @@ -1,716 +0,0 @@ -/* -Redefine these types for these reasons: -1) We can better control what properties are option and what are not. -2) We have to replace definitions with our definitions which use our typescript-estree types. -3) We can better document the fields so it's easier for new contributers to understand. - -The def is wrapped up in a fake module so that it can be used in eslint-rules.d.ts -*/ - -declare module 'ts-eslint' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; - import { ParserServices } from '@typescript-eslint/parser'; - import { Linter } from 'eslint'; - import { JSONSchema4 } from 'json-schema'; - - //#region SourceCode - - interface Program extends TSESTree.Program { - comments: TSESTree.Comment[]; - tokens: TSESTree.Token[]; - loc: TSESTree.SourceLocation; - range: TSESTree.Range; - } - - namespace SourceCode { - export interface Config { - text: string; - ast: Program; - parserServices?: ParserServices; - scopeManager?: Scope.ScopeManager; - visitorKeys?: VisitorKeys; - } - - export interface VisitorKeys { - [nodeType: string]: string[]; - } - - export type FilterPredicate = ( - tokenOrComment: TSESTree.Token | TSESTree.Comment, - ) => boolean; - - export type CursorWithSkipOptions = - | number - | FilterPredicate - | { - includeComments?: boolean; - filter?: FilterPredicate; - skip?: number; - }; - - export type CursorWithCountOptions = - | number - | FilterPredicate - | { - includeComments?: boolean; - filter?: FilterPredicate; - count?: number; - }; - } - - class SourceCode { - text: string; - ast: Program; - lines: string[]; - hasBOM: boolean; - parserServices: ParserServices; - scopeManager: Scope.ScopeManager; - visitorKeys: SourceCode.VisitorKeys; - tokensAndComments: (TSESTree.Comment | TSESTree.Token)[]; - - constructor(text: string, ast: Program); - constructor(config: SourceCode.Config); - - static splitLines(text: string): string[]; - - getText( - node?: TSESTree.Node, - beforeCount?: number, - afterCount?: number, - ): string; - - getLines(): string[]; - - getAllComments(): TSESTree.Comment[]; - - getComments( - node: TSESTree.Node, - ): { leading: TSESTree.Comment[]; trailing: TSESTree.Comment[] }; - - getJSDocComment(node: TSESTree.Node): TSESTree.Node | TSESTree.Token | null; - - getNodeByRangeIndex(index: number): TSESTree.Node | null; - - isSpaceBetweenTokens( - first: TSESTree.Token, - second: TSESTree.Token, - ): boolean; - - getLocFromIndex(index: number): TSESTree.LineAndColumnData; - - getIndexFromLoc(location: TSESTree.LineAndColumnData): number; - - // Inherited methods from TokenStore - // --------------------------------- - - getTokenByRangeStart( - offset: number, - options?: { includeComments?: boolean }, - ): TSESTree.Token | null; - - getFirstToken( - node: TSESTree.Node, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getFirstTokens( - node: TSESTree.Node, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getLastToken( - node: TSESTree.Node, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getLastTokens( - node: TSESTree.Node, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokenBefore( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getTokensBefore( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokenAfter( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getTokensAfter( - node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getFirstTokenBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getFirstTokensBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getLastTokenBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithSkipOptions, - ): TSESTree.Token | null; - - getLastTokensBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - options?: SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokensBetween( - left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, - padding?: - | number - | SourceCode.FilterPredicate - | SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - getTokens( - node: TSESTree.Node, - beforeCount?: number, - afterCount?: number, - ): TSESTree.Token[]; - getTokens( - node: TSESTree.Node, - options: SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions, - ): TSESTree.Token[]; - - commentsExistBetween( - left: TSESTree.Node | TSESTree.Token, - right: TSESTree.Node | TSESTree.Token, - ): boolean; - - getCommentsBefore( - nodeOrToken: TSESTree.Node | TSESTree.Token, - ): TSESTree.Comment[]; - - getCommentsAfter( - nodeOrToken: TSESTree.Node | TSESTree.Token, - ): TSESTree.Comment[]; - - getCommentsInside(node: TSESTree.Node): TSESTree.Comment[]; - } - - //#endregion SourceCode - - //#region Rule - - interface RuleMetaDataDocs { - /** - * The general category the rule falls within - */ - category: - | 'Best Practices' - | 'Stylistic Issues' - | 'Variables' - | 'Possible Errors'; - /** - * Concise description of the rule - */ - description: string; - /** - * Extra information linking the rule to a tslint rule - */ - extraDescription?: string[]; - /** - * The recommendation level for the rule. - * Used by the build tools to generate the recommended config. - * Set to false to not include it as a recommendation - */ - recommended: 'error' | 'warn' | false; - /** - * The URL of the rule's docs - */ - url: string; - } - interface RuleMetaData { - /** - * True if the rule is deprecated, false otherwise - */ - deprecated?: boolean; - /** - * Documentation for the rule - */ - docs: RuleMetaDataDocs; - /** - * The fixer category. Omit if there is no fixer - */ - fixable?: 'code' | 'whitespace'; - /** - * A map of messages which the rule can report. - * The key is the messageId, and the string is the parameterised error string. - * See: https://eslint.org/docs/developer-guide/working-with-rules#messageids - */ - messages: Record; - /** - * The type of rule. - * - `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. - * - `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn’t changed. - * - `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren’t specified in the AST. - */ - type: 'suggestion' | 'problem' | 'layout'; - /** - * The name of the rule this rule was replaced by, if it was deprecated. - */ - replacedBy?: string; - /** - * The options schema. Supply an empty array if there are no options. - */ - schema: JSONSchema4 | JSONSchema4[]; - } - - interface RuleFix { - range: TSESTree.Range; - text: string; - } - - interface RuleFixer { - insertTextAfter( - nodeOrToken: TSESTree.Node | TSESTree.Token, - text: string, - ): RuleFix; - - insertTextAfterRange(range: TSESTree.Range, text: string): RuleFix; - - insertTextBefore( - nodeOrToken: TSESTree.Node | TSESTree.Token, - text: string, - ): RuleFix; - - insertTextBeforeRange(range: TSESTree.Range, text: string): RuleFix; - - remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix; - - removeRange(range: TSESTree.Range): RuleFix; - - replaceText( - nodeOrToken: TSESTree.Node | TSESTree.Token, - text: string, - ): RuleFix; - - replaceTextRange(range: TSESTree.Range, text: string): RuleFix; - } - - type ReportFixFunction = ( - fixer: RuleFixer, - ) => null | RuleFix | RuleFix[] | IterableIterator; - - interface ReportDescriptor { - /** - * The parameters for the message string associated with `messageId`. - */ - data?: Record; - /** - * The fixer function. - */ - fix?: ReportFixFunction | null; - /** - * The messageId which is being reported. - */ - messageId: TMessageIds; - /** - * The Node or AST Token which the report is being attached to - */ - node: TSESTree.Node | TSESTree.Comment | TSESTree.Token; - /** - * An override of the location of the report - */ - loc?: TSESTree.SourceLocation | TSESTree.LineAndColumnData; - } - - interface RuleContext< - TMessageIds extends string, - TOptions extends Readonly - > { - /** - * The rule ID. - */ - id: string; - /** - * An array of the configured options for this rule. - * This array does not include the rule severity. - */ - options: TOptions; - /** - * The shared settings from configuration. - * We do not have any shared settings in this plugin. - */ - settings: {}; - /** - * The name of the parser from configuration. - */ - parserPath: string; - /** - * The parser options configured for this run - */ - parserOptions: Linter.ParserOptions; - /** - * An object containing parser-provided services for rules - */ - parserServices?: ParserServices; - - /** - * Returns an array of the ancestors of the currently-traversed node, starting at - * the root of the AST and continuing through the direct parent of the current node. - * This array does not include the currently-traversed node itself. - */ - getAncestors(): TSESTree.Node[]; - - /** - * Returns a list of variables declared by the given node. - * This information can be used to track references to variables. - */ - getDeclaredVariables(node: TSESTree.Node): Scope.Variable[]; - - /** - * Returns the filename associated with the source. - */ - getFilename(): string; - - /** - * Returns the scope of the currently-traversed node. - * This information can be used track references to variables. - */ - getScope(): Scope.Scope; - - /** - * Returns a SourceCode object that you can use to work with the source that - * was passed to ESLint. - */ - getSourceCode(): SourceCode; - - /** - * Marks a variable with the given name in the current scope as used. - * This affects the no-unused-vars rule. - */ - markVariableAsUsed(name: string): boolean; - - /** - * Reports a problem in the code. - */ - report(descriptor: ReportDescriptor): void; - } - - // This isn't the correct signature, but it makes it easier to do custom unions within reusable listneers - // never will break someone's code unless they specifically type the function argument - type RuleFunction = (node: T) => void; - - interface RuleListener { - [nodeSelector: string]: RuleFunction | undefined; - ArrayExpression?: RuleFunction; - ArrayPattern?: RuleFunction; - ArrowFunctionExpression?: RuleFunction; - AssignmentPattern?: RuleFunction; - AssignmentExpression?: RuleFunction; - AwaitExpression?: RuleFunction; - BlockStatement?: RuleFunction; - BreakStatement?: RuleFunction; - CallExpression?: RuleFunction; - CatchClause?: RuleFunction; - ClassBody?: RuleFunction; - ClassDeclaration?: RuleFunction; - ClassExpression?: RuleFunction; - ClassProperty?: RuleFunction; - Comment?: RuleFunction; - ConditionalExpression?: RuleFunction; - ContinueStatement?: RuleFunction; - DebuggerStatement?: RuleFunction; - Decorator?: RuleFunction; - DoWhileStatement?: RuleFunction; - EmptyStatement?: RuleFunction; - ExportAllDeclaration?: RuleFunction; - ExportDefaultDeclaration?: RuleFunction; - ExportNamedDeclaration?: RuleFunction; - ExportSpecifier?: RuleFunction; - ExpressionStatement?: RuleFunction; - ForInStatement?: RuleFunction; - ForOfStatement?: RuleFunction; - ForStatement?: RuleFunction; - Identifier?: RuleFunction; - IfStatement?: RuleFunction; - Import?: RuleFunction; - ImportDeclaration?: RuleFunction; - ImportDefaultSpecifier?: RuleFunction; - ImportNamespaceSpecifier?: RuleFunction; - ImportSpecifier?: RuleFunction; - JSXAttribute?: RuleFunction; - JSXClosingElement?: RuleFunction; - JSXClosingFragment?: RuleFunction; - JSXElement?: RuleFunction; - JSXEmptyExpression?: RuleFunction; - JSXExpressionContainer?: RuleFunction; - JSXFragment?: RuleFunction; - JSXIdentifier?: RuleFunction; - JSXMemberExpression?: RuleFunction; - JSXOpeningElement?: RuleFunction; - JSXOpeningFragment?: RuleFunction; - JSXSpreadAttribute?: RuleFunction; - JSXSpreadChild?: RuleFunction; - JSXText?: RuleFunction; - LabeledStatement?: RuleFunction; - MemberExpression?: RuleFunction; - MetaProperty?: RuleFunction; - MethodDefinition?: RuleFunction; - NewExpression?: RuleFunction; - ObjectExpression?: RuleFunction; - ObjectPattern?: RuleFunction; - Program?: RuleFunction; - Property?: RuleFunction; - RestElement?: RuleFunction; - ReturnStatement?: RuleFunction; - SequenceExpression?: RuleFunction; - SpreadElement?: RuleFunction; - Super?: RuleFunction; - SwitchCase?: RuleFunction; - SwitchStatement?: RuleFunction; - TaggedTemplateExpression?: RuleFunction; - TemplateElement?: RuleFunction; - TemplateLiteral?: RuleFunction; - ThisExpression?: RuleFunction; - ThrowStatement?: RuleFunction; - Token?: RuleFunction; - TryStatement?: RuleFunction; - TSAbstractKeyword?: RuleFunction; - TSAbstractMethodDefinition?: RuleFunction< - TSESTree.TSAbstractMethodDefinition - >; - TSAnyKeyword?: RuleFunction; - TSArrayType?: RuleFunction; - TSAsExpression?: RuleFunction; - TSAsyncKeyword?: RuleFunction; - TSBigIntKeyword?: RuleFunction; - TSBooleanKeyword?: RuleFunction; - TSCallSignatureDeclaration?: RuleFunction< - TSESTree.TSCallSignatureDeclaration - >; - TSConditionalType?: RuleFunction; - TSConstructSignatureDeclaration?: RuleFunction< - TSESTree.TSConstructSignatureDeclaration - >; - TSDeclareKeyword?: RuleFunction; - TSDeclareFunction?: RuleFunction; - TSEnumDeclaration?: RuleFunction; - TSEnumMember?: RuleFunction; - TSExportAssignment?: RuleFunction; - TSExportKeyword?: RuleFunction; - TSExternalModuleReference?: RuleFunction< - TSESTree.TSExternalModuleReference - >; - TSImportEqualsDeclaration?: RuleFunction< - TSESTree.TSImportEqualsDeclaration - >; - TSImportType?: RuleFunction; - TSIndexedAccessType?: RuleFunction; - TSIndexSignature?: RuleFunction; - TSInferType?: RuleFunction; - TSInterfaceBody?: RuleFunction; - TSInterfaceDeclaration?: RuleFunction; - TSIntersectionType?: RuleFunction; - TSLiteralType?: RuleFunction; - TSMappedType?: RuleFunction; - TSMethodSignature?: RuleFunction; - TSModuleBlock?: RuleFunction; - TSModuleDeclaration?: RuleFunction; - TSNamespaceExportDeclaration?: RuleFunction< - TSESTree.TSNamespaceExportDeclaration - >; - TSNeverKeyword?: RuleFunction; - TSNonNullExpression?: RuleFunction; - TSNullKeyword?: RuleFunction; - TSNumberKeyword?: RuleFunction; - TSObjectKeyword?: RuleFunction; - TSOptionalType?: RuleFunction; - TSParameterProperty?: RuleFunction; - TSParenthesizedType?: RuleFunction; - TSPrivateKeyword?: RuleFunction; - TSPropertySignature?: RuleFunction; - TSProtectedKeyword?: RuleFunction; - TSPublicKeyword?: RuleFunction; - TSQualifiedName?: RuleFunction; - TSReadonlyKeyword?: RuleFunction; - TSRestType?: RuleFunction; - TSStaticKeyword?: RuleFunction; - TSStringKeyword?: RuleFunction; - TSSymbolKeyword?: RuleFunction; - TSThisType?: RuleFunction; - TSTupleType?: RuleFunction; - TSTypeAliasDeclaration?: RuleFunction; - TSTypeAnnotation?: RuleFunction; - TSTypeAssertion?: RuleFunction; - TSTypeLiteral?: RuleFunction; - TSTypeOperator?: RuleFunction; - TSTypeParameter?: RuleFunction; - TSTypeParameterDeclaration?: RuleFunction< - TSESTree.TSTypeParameterDeclaration - >; - TSTypeParameterInstantiation?: RuleFunction< - TSESTree.TSTypeParameterInstantiation - >; - TSTypePredicate?: RuleFunction; - TSTypeQuery?: RuleFunction; - TSTypeReference?: RuleFunction; - TSUndefinedKeyword?: RuleFunction; - TSUnionType?: RuleFunction; - TSUnknownKeyword?: RuleFunction; - TSVoidKeyword?: RuleFunction; - UnaryExpression?: RuleFunction; - UpdateExpression?: RuleFunction; - VariableDeclaration?: RuleFunction; - VariableDeclarator?: RuleFunction; - WhileStatement?: RuleFunction; - WithStatement?: RuleFunction; - YieldExpression?: RuleFunction; - } - - interface RuleModule< - TMessageIds extends string, - TOptions extends Readonly, - // for extending base rules - TRuleListener extends RuleListener = RuleListener - > { - /** - * Metadata about the rule - */ - meta: RuleMetaData; - - /** - * Function which returns an object with methods that ESLint calls to “visit” - * nodes while traversing the abstract syntax tree. - */ - create(context: RuleContext): TRuleListener; - } - - //#endregion Rule - - namespace Scope { - interface ScopeManager { - scopes: Scope[]; - globalScope: Scope | null; - - acquire(node: TSESTree.Node, inner?: boolean): Scope | null; - - getDeclaredVariables(node: TSESTree.Node): Variable[]; - } - - interface Reference { - identifier: TSESTree.Identifier; - from: Scope; - resolved: Variable | null; - writeExpr: TSESTree.Node | null; - init: boolean; - - isWrite(): boolean; - - isRead(): boolean; - - isWriteOnly(): boolean; - - isReadOnly(): boolean; - - isReadWrite(): boolean; - } - - interface Variable { - name: string; - identifiers: TSESTree.Identifier[]; - references: Reference[]; - defs: Definition[]; - scope: Scope; - eslintUsed?: boolean; - } - - interface Scope { - type: - | 'block' - | 'catch' - | 'class' - | 'for' - | 'function' - | 'function-expression-name' - | 'global' - | 'module' - | 'switch' - | 'with' - | 'TDZ'; - isStrict: boolean; - upper: Scope | null; - childScopes: Scope[]; - variableScope: Scope; - block: TSESTree.Node; - variables: Variable[]; - set: Map; - references: Reference[]; - through: Reference[]; - functionExpressionScope: boolean; - } - - type DefinitionType = - | { type: 'CatchClause'; node: TSESTree.CatchClause; parent: null } - | { - type: 'ClassName'; - node: TSESTree.ClassDeclaration | TSESTree.ClassExpression; - parent: null; - } - | { - type: 'FunctionName'; - node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; - parent: null; - } - | { type: 'ImplicitGlobalVariable'; node: TSESTree.Program; parent: null } - | { - type: 'ImportBinding'; - node: - | TSESTree.ImportSpecifier - | TSESTree.ImportDefaultSpecifier - | TSESTree.ImportNamespaceSpecifier; - parent: TSESTree.ImportDeclaration; - } - | { - type: 'Parameter'; - node: - | TSESTree.FunctionDeclaration - | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression; - parent: null; - } - | { type: 'TDZ'; node: any; parent: null } - | { - type: 'Variable'; - node: TSESTree.VariableDeclarator; - parent: TSESTree.VariableDeclaration; - }; - - type Definition = DefinitionType & { name: TSESTree.Identifier }; - } - - export { - ReportDescriptor, - ReportFixFunction, - RuleContext, - RuleFix, - RuleFixer, - RuleFunction, - RuleListener, - RuleMetaData, - RuleMetaDataDocs, - Scope, - SourceCode, - }; - export default RuleModule; -} diff --git a/packages/experimental-utils/LICENSE b/packages/experimental-utils/LICENSE new file mode 100644 index 00000000000..7e7370143b2 --- /dev/null +++ b/packages/experimental-utils/LICENSE @@ -0,0 +1,21 @@ +MIT License + +Copyright (c) 2019 TypeScript ESLint and other contributors + +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: + +The above copyright notice and this permission notice shall be included in all +copies or substantial portions of the Software. + +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE +SOFTWARE. diff --git a/packages/experimental-utils/README.md b/packages/experimental-utils/README.md new file mode 100644 index 00000000000..45ecc1a64f8 --- /dev/null +++ b/packages/experimental-utils/README.md @@ -0,0 +1,33 @@ +# @typescript-eslint/experimental-utils + +(Experimental) Utilities for working with TypeScript + ESLint together. + +## Note + +This package has inherited its version number from the @typescript-eslint project. +Meaning that even though this package is `1.x.y`, you shouldn't expect 100% stability between minor version bumps. +i.e. treat it as a `0.x.y` package. + +Feel free to use it now, and let us know what utilities you need or send us PRs with utilities you build on top of it. + +Once it is stable, it will be renamed to `@typescript-eslint/util` for a `2.0.0` release. + +## Exports + +| Name | Description | +| --------------------------- | ---------------------------------------------------------------------------------------------- | +| [`TSESTree`] | Types for the TypeScript flavour of ESTree created by `@typescript-eslint/typescript-estree`. | +| [`AST_NODE_TYPES`] | An enum with the names of every single _node_ found in `TSESTree`. | +| [`AST_TOKEN_TYPES`] | An enum with the names of every single _token_ found in `TSESTree`. | +| [`TSESLint`] | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | +| [`ESLintUtils`] | Tools for creating eslint rules with TypeScript. | +| [`ESLintUtils.RuleCreator`] | A function for creating strictly typed eslint rules with TypeScript. | +| [`ParserServices`] | The parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | + +[`AST_NODE_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) +[`AST_TOKEN_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) +[`ESLintUtils`](./src/eslint-utils) +[`ESLintUtils.createRule`](./src/eslint-utils/createRule.ts) +[`ParserServices`](../packages/typescript-estree/src/ts-estree/parser.ts) +[`TSESTree`](../packages/typescript-estree/src/ts-estree/ts-estree.ts) +[`TSESLint`](./src/ts-eslint) diff --git a/packages/experimental-utils/jest.config.js b/packages/experimental-utils/jest.config.js new file mode 100644 index 00000000000..b64d433b01a --- /dev/null +++ b/packages/experimental-utils/jest.config.js @@ -0,0 +1,13 @@ +'use strict'; + +module.exports = { + testEnvironment: 'node', + transform: { + '^.+\\.tsx?$': 'ts-jest', + }, + testRegex: './tests/.+\\.test\\.ts$', + collectCoverage: false, + collectCoverageFrom: ['src/**/*.{js,jsx,ts,tsx}'], + moduleFileExtensions: ['ts', 'tsx', 'js', 'jsx', 'json', 'node'], + coverageReporters: ['text-summary', 'lcov'], +}; diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json new file mode 100644 index 00000000000..d1ab0c20078 --- /dev/null +++ b/packages/experimental-utils/package.json @@ -0,0 +1,40 @@ +{ + "name": "@typescript-eslint/experimental-utils", + "version": "1.7.0", + "description": "(Experimental) Utilities for working with TypeScript + ESLint together", + "keywords": [ + "eslint", + "typescript", + "estree" + ], + "engines": { + "node": "^6.14.0 || ^8.10.0 || >=9.10.0" + }, + "files": [ + "dist", + "package.json", + "README.md", + "LICENSE" + ], + "repository": "typescript-eslint/typescript-eslint", + "bugs": { + "url": "https://github.com/typescript-eslint/typescript-eslint/issues" + }, + "license": "MIT", + "main": "dist/index.js", + "types": "dist/index.d.ts", + "scripts": { + "test": "jest --coverage", + "prebuild": "npm run clean", + "build": "tsc -p tsconfig.build.json", + "clean": "rimraf dist/", + "typecheck": "tsc --noEmit" + }, + "dependencies": { + "@typescript-eslint/typescript-estree": "1.7.0" + }, + "devDependencies": {}, + "peerDependencies": { + "typescript": "*" + } +} diff --git a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts new file mode 100644 index 00000000000..b20e70af213 --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts @@ -0,0 +1,65 @@ +import { + RuleMetaData, + RuleMetaDataDocs, + RuleListener, + RuleContext, + RuleModule, +} from '../ts-eslint/Rule'; +import { applyDefault } from './applyDefault'; + +// Utility type to remove a list of properties from an object +type RemoveProps< + TObj extends Record, + TKeys extends keyof TObj +> = Pick>; + +// we'll automatically add the url + tslint description for people. +type CreateRuleMetaDocs = RemoveProps & { + tslintName?: string; +}; +type CreateRuleMeta = { + docs: CreateRuleMetaDocs; +} & RemoveProps, 'docs'>; + +export function RuleCreator(urlCreator: (ruleName: string) => string) { + // This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 + // TODO - when the above PR lands; add type checking for the context.report `data` property + return function createRule< + TOptions extends any[], + TMessageIds extends string, + TRuleListener extends RuleListener = RuleListener + >({ + name, + meta, + defaultOptions, + create, + }: { + name: string; + meta: CreateRuleMeta; + defaultOptions: TOptions; + create: ( + context: RuleContext, + optionsWithDefault: TOptions, + ) => TRuleListener; + }): RuleModule { + return { + meta: { + ...meta, + docs: { + ...meta.docs, + url: urlCreator(name), + extraDescription: meta.docs.tslintName + ? [`\`${meta.docs.tslintName}\` from TSLint`] + : undefined, + }, + }, + create(context) { + const optionsWithDefault = applyDefault( + defaultOptions, + context.options, + ); + return create(context, optionsWithDefault); + }, + }; + }; +} diff --git a/packages/eslint-plugin/src/util/applyDefault.ts b/packages/experimental-utils/src/eslint-utils/applyDefault.ts similarity index 100% rename from packages/eslint-plugin/src/util/applyDefault.ts rename to packages/experimental-utils/src/eslint-utils/applyDefault.ts diff --git a/packages/experimental-utils/src/eslint-utils/batchedSingleLineTests.ts b/packages/experimental-utils/src/eslint-utils/batchedSingleLineTests.ts new file mode 100644 index 00000000000..0812adade32 --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/batchedSingleLineTests.ts @@ -0,0 +1,59 @@ +import { ValidTestCase, InvalidTestCase } from '../ts-eslint'; + +/** + * Converts a batch of single line tests into a number of separate test cases. + * This makes it easier to write tests which use the same options. + * + * Why wouldn't you just leave them as one test? + * Because it makes the test error messages harder to decipher. + * This way each line will fail separately, instead of them all failing together. + */ +function batchedSingleLineTests>( + test: ValidTestCase, +): ValidTestCase[]; +/** + * Converts a batch of single line tests into a number of separate test cases. + * This makes it easier to write tests which use the same options. + * + * Why wouldn't you just leave them as one test? + * Because it makes the test error messages harder to decipher. + * This way each line will fail separately, instead of them all failing together. + * + * Make sure you have your line numbers correct for error reporting, as it will match + * the line numbers up with the split tests! + */ +function batchedSingleLineTests< + TMessageIds extends string, + TOptions extends Readonly +>( + test: InvalidTestCase, +): InvalidTestCase[]; +function batchedSingleLineTests< + TMessageIds extends string, + TOptions extends Readonly +>( + options: ValidTestCase | InvalidTestCase, +): (ValidTestCase | InvalidTestCase)[] { + // eslint counts lines from 1 + const lineOffset = options.code[0] === '\n' ? 2 : 1; + return options.code + .trim() + .split('\n') + .map((code, i) => { + const lineNum = i + lineOffset; + const errors = + 'errors' in options + ? options.errors.filter(e => e.line === lineNum) + : []; + return { + ...options, + code, + errors: errors.map(e => ({ + ...e, + line: 1, + })), + }; + }); +} + +export { batchedSingleLineTests }; diff --git a/packages/eslint-plugin/src/util/deepMerge.ts b/packages/experimental-utils/src/eslint-utils/deepMerge.ts similarity index 100% rename from packages/eslint-plugin/src/util/deepMerge.ts rename to packages/experimental-utils/src/eslint-utils/deepMerge.ts diff --git a/packages/experimental-utils/src/eslint-utils/index.ts b/packages/experimental-utils/src/eslint-utils/index.ts new file mode 100644 index 00000000000..c8069ca6fc2 --- /dev/null +++ b/packages/experimental-utils/src/eslint-utils/index.ts @@ -0,0 +1,4 @@ +export * from './applyDefault'; +export * from './batchedSingleLineTests'; +export * from './RuleCreator'; +export * from './deepMerge'; diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts new file mode 100644 index 00000000000..8b3a7f039ff --- /dev/null +++ b/packages/experimental-utils/src/index.ts @@ -0,0 +1,13 @@ +import * as ESLintUtils from './eslint-utils'; +import * as TSESLint from './ts-eslint'; + +export { ESLintUtils, TSESLint }; + +// for convenience's sake - export the types directly from here so consumers +// don't need to reference/install both packages in their code +export { + AST_NODE_TYPES, + AST_TOKEN_TYPES, + ParserServices, + TSESTree, +} from '@typescript-eslint/typescript-estree'; diff --git a/packages/experimental-utils/src/ts-eslint/AST.ts b/packages/experimental-utils/src/ts-eslint/AST.ts new file mode 100644 index 00000000000..1c77caafedf --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/AST.ts @@ -0,0 +1,18 @@ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { + TSESTree, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; + +namespace AST { + export type TokenType = AST_TOKEN_TYPES; + + export type Token = TSESTree.Token; + + export type SourceLocation = TSESTree.SourceLocation; + + export type Range = TSESTree.Range; +} + +export { AST }; diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts new file mode 100644 index 00000000000..cff921e048c --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -0,0 +1,132 @@ +/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ + +import { TSESTree, ParserServices } from '@typescript-eslint/typescript-estree'; +import { RuleModule, RuleFix } from './Rule'; +import { Scope } from './Scope'; +import { SourceCode } from './SourceCode'; + +declare class Linter { + version: string; + + verify( + code: SourceCode | string, + config: Linter.Config, + filename?: string, + ): Linter.LintMessage[]; + verify( + code: SourceCode | string, + config: Linter.Config, + options: Linter.LintOptions, + ): Linter.LintMessage[]; + + verifyAndFix( + code: string, + config: Linter.Config, + filename?: string, + ): Linter.FixReport; + verifyAndFix( + code: string, + config: Linter.Config, + options: Linter.FixOptions, + ): Linter.FixReport; + + getSourceCode(): SourceCode; + + defineRule( + name: string, + rule: RuleModule, + ): void; + + defineRules( + rules: Record>, + ): void; + + getRules(): Map< + string, + RuleModule + >; + + defineParser(name: string, parser: Linter.ParserModule): void; +} + +namespace Linter { + export type Severity = 0 | 1 | 2; + export type RuleLevel = Severity | 'off' | 'warn' | 'error'; + + export interface RuleLevelAndOptions extends Array { + 0: RuleLevel; + } + + export interface Config { + rules?: { + [name: string]: RuleLevel | RuleLevelAndOptions; + }; + parser?: string; + parserOptions?: ParserOptions; + settings?: { [name: string]: any }; + env?: { [name: string]: boolean }; + globals?: { [name: string]: boolean }; + } + + export interface ParserOptions { + ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 2015 | 2016 | 2017 | 2018; + sourceType?: 'script' | 'module'; + ecmaFeatures?: { + globalReturn?: boolean; + impliedStrict?: boolean; + jsx?: boolean; + experimentalObjectRestSpread?: boolean; + [key: string]: any; + }; + [key: string]: any; + } + + export interface LintOptions { + filename?: string; + preprocess?: (code: string) => string[]; + postprocess?: (problemLists: LintMessage[][]) => LintMessage[]; + allowInlineConfig?: boolean; + reportUnusedDisableDirectives?: boolean; + } + + export interface LintMessage { + column: number; + line: number; + endColumn?: number; + endLine?: number; + ruleId: string | null; + message: string; + nodeType: string; + fatal?: true; + severity: Severity; + fix?: RuleFix; + source: string | null; + } + + export interface FixOptions extends LintOptions { + fix?: boolean; + } + + export interface FixReport { + fixed: boolean; + output: string; + messages: LintMessage[]; + } + + export type ParserModule = + | { + parse(text: string, options?: any): TSESTree.Program; + } + | { + parseForESLint(text: string, options?: any): ESLintParseResult; + }; + + export interface ESLintParseResult { + ast: TSESTree.Program; + parserServices?: ParserServices; + scopeManager?: Scope.ScopeManager; + visitorKeys?: SourceCode.VisitorKeys; + } +} + +export { Linter }; diff --git a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts new file mode 100644 index 00000000000..d374ac57b91 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts @@ -0,0 +1,21 @@ +export interface ParserOptions { + loc?: boolean; + comment?: boolean; + range?: boolean; + tokens?: boolean; + sourceType?: 'script' | 'module'; + ecmaVersion?: number; + ecmaFeatures?: { + globalReturn?: boolean; + jsx?: boolean; + }; + // ts-estree specific + filePath?: string; + project?: string | string[]; + useJSXTextNode?: boolean; + errorOnUnknownASTType?: boolean; + errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; + tsconfigRootDir?: string; + extraFileExtensions?: string[]; + warnOnUnsupportedTypeScriptVersion?: boolean; +} diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts new file mode 100644 index 00000000000..48162df0867 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -0,0 +1,400 @@ +import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; +import { JSONSchema4 } from 'json-schema'; +import { AST } from './AST'; +import { Linter } from './Linter'; +import { Scope } from './Scope'; +import { SourceCode } from './SourceCode'; + +interface RuleMetaDataDocs { + /** + * The general category the rule falls within + */ + category: + | 'Best Practices' + | 'Stylistic Issues' + | 'Variables' + | 'Possible Errors'; + /** + * Concise description of the rule + */ + description: string; + /** + * Extra information linking the rule to a tslint rule + */ + extraDescription?: string[]; + /** + * The recommendation level for the rule. + * Used by the build tools to generate the recommended config. + * Set to false to not include it as a recommendation + */ + recommended: 'error' | 'warn' | false; + /** + * The URL of the rule's docs + */ + url: string; +} +interface RuleMetaData { + /** + * True if the rule is deprecated, false otherwise + */ + deprecated?: boolean; + /** + * Documentation for the rule + */ + docs: RuleMetaDataDocs; + /** + * The fixer category. Omit if there is no fixer + */ + fixable?: 'code' | 'whitespace'; + /** + * A map of messages which the rule can report. + * The key is the messageId, and the string is the parameterised error string. + * See: https://eslint.org/docs/developer-guide/working-with-rules#messageids + */ + messages: Record; + /** + * The type of rule. + * - `"problem"` means the rule is identifying code that either will cause an error or may cause a confusing behavior. Developers should consider this a high priority to resolve. + * - `"suggestion"` means the rule is identifying something that could be done in a better way but no errors will occur if the code isn’t changed. + * - `"layout"` means the rule cares primarily about whitespace, semicolons, commas, and parentheses, all the parts of the program that determine how the code looks rather than how it executes. These rules work on parts of the code that aren’t specified in the AST. + */ + type: 'suggestion' | 'problem' | 'layout'; + /** + * The name of the rule this rule was replaced by, if it was deprecated. + */ + replacedBy?: string; + /** + * The options schema. Supply an empty array if there are no options. + */ + schema: JSONSchema4 | JSONSchema4[]; +} + +interface RuleFix { + range: AST.Range; + text: string; +} + +interface RuleFixer { + insertTextAfter( + nodeOrToken: TSESTree.Node | TSESTree.Token, + text: string, + ): RuleFix; + + insertTextAfterRange(range: AST.Range, text: string): RuleFix; + + insertTextBefore( + nodeOrToken: TSESTree.Node | TSESTree.Token, + text: string, + ): RuleFix; + + insertTextBeforeRange(range: AST.Range, text: string): RuleFix; + + remove(nodeOrToken: TSESTree.Node | TSESTree.Token): RuleFix; + + removeRange(range: AST.Range): RuleFix; + + replaceText( + nodeOrToken: TSESTree.Node | TSESTree.Token, + text: string, + ): RuleFix; + + replaceTextRange(range: AST.Range, text: string): RuleFix; +} + +type ReportFixFunction = ( + fixer: RuleFixer, +) => null | RuleFix | RuleFix[] | IterableIterator; + +interface ReportDescriptor { + /** + * The parameters for the message string associated with `messageId`. + */ + data?: Record; + /** + * The fixer function. + */ + fix?: ReportFixFunction | null; + /** + * The messageId which is being reported. + */ + messageId: TMessageIds; + /** + * The Node or AST Token which the report is being attached to + */ + node: TSESTree.Node | TSESTree.Comment | TSESTree.Token; + /** + * An override of the location of the report + */ + loc?: TSESTree.SourceLocation | TSESTree.LineAndColumnData; +} + +interface RuleContext< + TMessageIds extends string, + TOptions extends Readonly +> { + /** + * The rule ID. + */ + id: string; + /** + * An array of the configured options for this rule. + * This array does not include the rule severity. + */ + options: TOptions; + /** + * The shared settings from configuration. + * We do not have any shared settings in this plugin. + */ + settings: {}; + /** + * The name of the parser from configuration. + */ + parserPath: string; + /** + * The parser options configured for this run + */ + parserOptions: Linter.ParserOptions; + /** + * An object containing parser-provided services for rules + */ + parserServices?: ParserServices; + + /** + * Returns an array of the ancestors of the currently-traversed node, starting at + * the root of the AST and continuing through the direct parent of the current node. + * This array does not include the currently-traversed node itself. + */ + getAncestors(): TSESTree.Node[]; + + /** + * Returns a list of variables declared by the given node. + * This information can be used to track references to variables. + */ + getDeclaredVariables(node: TSESTree.Node): Scope.Variable[]; + + /** + * Returns the filename associated with the source. + */ + getFilename(): string; + + /** + * Returns the scope of the currently-traversed node. + * This information can be used track references to variables. + */ + getScope(): Scope.Scope; + + /** + * Returns a SourceCode object that you can use to work with the source that + * was passed to ESLint. + */ + getSourceCode(): SourceCode; + + /** + * Marks a variable with the given name in the current scope as used. + * This affects the no-unused-vars rule. + */ + markVariableAsUsed(name: string): boolean; + + /** + * Reports a problem in the code. + */ + report(descriptor: ReportDescriptor): void; +} + +// This isn't the correct signature, but it makes it easier to do custom unions within reusable listneers +// never will break someone's code unless they specifically type the function argument +type RuleFunction = (node: T) => void; + +interface RuleListener { + [nodeSelector: string]: RuleFunction | undefined; + ArrayExpression?: RuleFunction; + ArrayPattern?: RuleFunction; + ArrowFunctionExpression?: RuleFunction; + AssignmentPattern?: RuleFunction; + AssignmentExpression?: RuleFunction; + AwaitExpression?: RuleFunction; + BlockStatement?: RuleFunction; + BreakStatement?: RuleFunction; + CallExpression?: RuleFunction; + CatchClause?: RuleFunction; + ClassBody?: RuleFunction; + ClassDeclaration?: RuleFunction; + ClassExpression?: RuleFunction; + ClassProperty?: RuleFunction; + Comment?: RuleFunction; + ConditionalExpression?: RuleFunction; + ContinueStatement?: RuleFunction; + DebuggerStatement?: RuleFunction; + Decorator?: RuleFunction; + DoWhileStatement?: RuleFunction; + EmptyStatement?: RuleFunction; + ExportAllDeclaration?: RuleFunction; + ExportDefaultDeclaration?: RuleFunction; + ExportNamedDeclaration?: RuleFunction; + ExportSpecifier?: RuleFunction; + ExpressionStatement?: RuleFunction; + ForInStatement?: RuleFunction; + ForOfStatement?: RuleFunction; + ForStatement?: RuleFunction; + Identifier?: RuleFunction; + IfStatement?: RuleFunction; + Import?: RuleFunction; + ImportDeclaration?: RuleFunction; + ImportDefaultSpecifier?: RuleFunction; + ImportNamespaceSpecifier?: RuleFunction; + ImportSpecifier?: RuleFunction; + JSXAttribute?: RuleFunction; + JSXClosingElement?: RuleFunction; + JSXClosingFragment?: RuleFunction; + JSXElement?: RuleFunction; + JSXEmptyExpression?: RuleFunction; + JSXExpressionContainer?: RuleFunction; + JSXFragment?: RuleFunction; + JSXIdentifier?: RuleFunction; + JSXMemberExpression?: RuleFunction; + JSXOpeningElement?: RuleFunction; + JSXOpeningFragment?: RuleFunction; + JSXSpreadAttribute?: RuleFunction; + JSXSpreadChild?: RuleFunction; + JSXText?: RuleFunction; + LabeledStatement?: RuleFunction; + MemberExpression?: RuleFunction; + MetaProperty?: RuleFunction; + MethodDefinition?: RuleFunction; + NewExpression?: RuleFunction; + ObjectExpression?: RuleFunction; + ObjectPattern?: RuleFunction; + Program?: RuleFunction; + Property?: RuleFunction; + RestElement?: RuleFunction; + ReturnStatement?: RuleFunction; + SequenceExpression?: RuleFunction; + SpreadElement?: RuleFunction; + Super?: RuleFunction; + SwitchCase?: RuleFunction; + SwitchStatement?: RuleFunction; + TaggedTemplateExpression?: RuleFunction; + TemplateElement?: RuleFunction; + TemplateLiteral?: RuleFunction; + ThisExpression?: RuleFunction; + ThrowStatement?: RuleFunction; + Token?: RuleFunction; + TryStatement?: RuleFunction; + TSAbstractKeyword?: RuleFunction; + TSAbstractMethodDefinition?: RuleFunction< + TSESTree.TSAbstractMethodDefinition + >; + TSAnyKeyword?: RuleFunction; + TSArrayType?: RuleFunction; + TSAsExpression?: RuleFunction; + TSAsyncKeyword?: RuleFunction; + TSBigIntKeyword?: RuleFunction; + TSBooleanKeyword?: RuleFunction; + TSCallSignatureDeclaration?: RuleFunction< + TSESTree.TSCallSignatureDeclaration + >; + TSConditionalType?: RuleFunction; + TSConstructSignatureDeclaration?: RuleFunction< + TSESTree.TSConstructSignatureDeclaration + >; + TSDeclareKeyword?: RuleFunction; + TSDeclareFunction?: RuleFunction; + TSEnumDeclaration?: RuleFunction; + TSEnumMember?: RuleFunction; + TSExportAssignment?: RuleFunction; + TSExportKeyword?: RuleFunction; + TSExternalModuleReference?: RuleFunction; + TSImportEqualsDeclaration?: RuleFunction; + TSImportType?: RuleFunction; + TSIndexedAccessType?: RuleFunction; + TSIndexSignature?: RuleFunction; + TSInferType?: RuleFunction; + TSInterfaceBody?: RuleFunction; + TSInterfaceDeclaration?: RuleFunction; + TSIntersectionType?: RuleFunction; + TSLiteralType?: RuleFunction; + TSMappedType?: RuleFunction; + TSMethodSignature?: RuleFunction; + TSModuleBlock?: RuleFunction; + TSModuleDeclaration?: RuleFunction; + TSNamespaceExportDeclaration?: RuleFunction< + TSESTree.TSNamespaceExportDeclaration + >; + TSNeverKeyword?: RuleFunction; + TSNonNullExpression?: RuleFunction; + TSNullKeyword?: RuleFunction; + TSNumberKeyword?: RuleFunction; + TSObjectKeyword?: RuleFunction; + TSOptionalType?: RuleFunction; + TSParameterProperty?: RuleFunction; + TSParenthesizedType?: RuleFunction; + TSPrivateKeyword?: RuleFunction; + TSPropertySignature?: RuleFunction; + TSProtectedKeyword?: RuleFunction; + TSPublicKeyword?: RuleFunction; + TSQualifiedName?: RuleFunction; + TSReadonlyKeyword?: RuleFunction; + TSRestType?: RuleFunction; + TSStaticKeyword?: RuleFunction; + TSStringKeyword?: RuleFunction; + TSSymbolKeyword?: RuleFunction; + TSThisType?: RuleFunction; + TSTupleType?: RuleFunction; + TSTypeAliasDeclaration?: RuleFunction; + TSTypeAnnotation?: RuleFunction; + TSTypeAssertion?: RuleFunction; + TSTypeLiteral?: RuleFunction; + TSTypeOperator?: RuleFunction; + TSTypeParameter?: RuleFunction; + TSTypeParameterDeclaration?: RuleFunction< + TSESTree.TSTypeParameterDeclaration + >; + TSTypeParameterInstantiation?: RuleFunction< + TSESTree.TSTypeParameterInstantiation + >; + TSTypePredicate?: RuleFunction; + TSTypeQuery?: RuleFunction; + TSTypeReference?: RuleFunction; + TSUndefinedKeyword?: RuleFunction; + TSUnionType?: RuleFunction; + TSUnknownKeyword?: RuleFunction; + TSVoidKeyword?: RuleFunction; + UnaryExpression?: RuleFunction; + UpdateExpression?: RuleFunction; + VariableDeclaration?: RuleFunction; + VariableDeclarator?: RuleFunction; + WhileStatement?: RuleFunction; + WithStatement?: RuleFunction; + YieldExpression?: RuleFunction; +} + +interface RuleModule< + TMessageIds extends string, + TOptions extends Readonly, + // for extending base rules + TRuleListener extends RuleListener = RuleListener +> { + /** + * Metadata about the rule + */ + meta: RuleMetaData; + + /** + * Function which returns an object with methods that ESLint calls to “visit” + * nodes while traversing the abstract syntax tree. + */ + create(context: RuleContext): TRuleListener; +} + +export { + ReportDescriptor, + ReportFixFunction, + RuleContext, + RuleFix, + RuleFixer, + RuleFunction, + RuleListener, + RuleMetaData, + RuleMetaDataDocs, + RuleModule, +}; diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts new file mode 100644 index 00000000000..fe9b4b80396 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -0,0 +1,76 @@ +import { + AST_NODE_TYPES, + AST_TOKEN_TYPES, +} from '@typescript-eslint/typescript-estree'; +import { ParserOptions } from './ParserOptions'; +import { RuleModule } from './Rule'; + +interface ValidTestCase> { + code: string; + options?: TOptions; + filename?: string; + parserOptions?: ParserOptions; + settings?: Record; + parser?: string; + globals?: Record; + env?: { + browser?: boolean; + }; +} + +interface InvalidTestCase< + TMessageIds extends string, + TOptions extends Readonly +> extends ValidTestCase { + errors: TestCaseError[]; + output?: string | null; +} + +interface TestCaseError { + messageId: TMessageIds; + data?: Record; + type?: AST_NODE_TYPES | AST_TOKEN_TYPES; + line?: number; + column?: number; +} + +interface RunTests< + TMessageIds extends string, + TOptions extends Readonly +> { + // RuleTester.run also accepts strings for valid cases + valid: (ValidTestCase | string)[]; + invalid: InvalidTestCase[]; +} + +interface RunTests< + TMessageIds extends string, + TOptions extends Readonly +> { + // RuleTester.run also accepts strings for valid cases + valid: (ValidTestCase | string)[]; + invalid: InvalidTestCase[]; +} +interface RuleTesterConfig { + parser: '@typescript-eslint/parser'; + parserOptions?: ParserOptions; +} +interface RuleTester { + // eslint-disable-next-line @typescript-eslint/no-misused-new + new (config?: RuleTesterConfig): RuleTester; + + run>( + name: string, + rule: RuleModule, + tests: RunTests, + ): void; +} + +export { + InvalidTestCase, + RuleTester, + RuleTesterConfig, + RunTests, + TestCaseError, + ValidTestCase, +}; diff --git a/packages/experimental-utils/src/ts-eslint/Scope.ts b/packages/experimental-utils/src/ts-eslint/Scope.ts new file mode 100644 index 00000000000..e6922d9df19 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/Scope.ts @@ -0,0 +1,106 @@ +/* eslint-disable @typescript-eslint/no-namespace */ + +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +namespace Scope { + export interface ScopeManager { + scopes: Scope[]; + globalScope: Scope | null; + + acquire(node: TSESTree.Node, inner?: boolean): Scope | null; + + getDeclaredVariables(node: TSESTree.Node): Variable[]; + } + + export interface Reference { + identifier: TSESTree.Identifier; + from: Scope; + resolved: Variable | null; + writeExpr: TSESTree.Node | null; + init: boolean; + + isWrite(): boolean; + + isRead(): boolean; + + isWriteOnly(): boolean; + + isReadOnly(): boolean; + + isReadWrite(): boolean; + } + + export interface Variable { + name: string; + identifiers: TSESTree.Identifier[]; + references: Reference[]; + defs: Definition[]; + scope: Scope; + eslintUsed?: boolean; + } + + export interface Scope { + type: + | 'block' + | 'catch' + | 'class' + | 'for' + | 'function' + | 'function-expression-name' + | 'global' + | 'module' + | 'switch' + | 'with' + | 'TDZ'; + isStrict: boolean; + upper: Scope | null; + childScopes: Scope[]; + variableScope: Scope; + block: TSESTree.Node; + variables: Variable[]; + set: Map; + references: Reference[]; + through: Reference[]; + functionExpressionScope: boolean; + } + + export type DefinitionType = + | { type: 'CatchClause'; node: TSESTree.CatchClause; parent: null } + | { + type: 'ClassName'; + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression; + parent: null; + } + | { + type: 'FunctionName'; + node: TSESTree.FunctionDeclaration | TSESTree.FunctionExpression; + parent: null; + } + | { type: 'ImplicitGlobalVariable'; node: TSESTree.Program; parent: null } + | { + type: 'ImportBinding'; + node: + | TSESTree.ImportSpecifier + | TSESTree.ImportDefaultSpecifier + | TSESTree.ImportNamespaceSpecifier; + parent: TSESTree.ImportDeclaration; + } + | { + type: 'Parameter'; + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression; + parent: null; + } + | { type: 'TDZ'; node: any; parent: null } + | { + type: 'Variable'; + node: TSESTree.VariableDeclarator; + parent: TSESTree.VariableDeclaration; + }; + + export type Definition = DefinitionType & { name: TSESTree.Identifier }; +} + +export { Scope }; diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts new file mode 100644 index 00000000000..abf3c3e6e8f --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -0,0 +1,193 @@ +/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ + +import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; +import { Scope } from './Scope'; + +namespace SourceCode { + export interface Program extends TSESTree.Program { + comments: TSESTree.Comment[]; + tokens: TSESTree.Token[]; + } + + export interface Config { + text: string; + ast: Program; + parserServices?: ParserServices; + scopeManager?: Scope.ScopeManager; + visitorKeys?: VisitorKeys; + } + + export interface VisitorKeys { + [nodeType: string]: string[]; + } + + export type FilterPredicate = ( + tokenOrComment: TSESTree.Token | TSESTree.Comment, + ) => boolean; + + export type CursorWithSkipOptions = + | number + | FilterPredicate + | { + includeComments?: boolean; + filter?: FilterPredicate; + skip?: number; + }; + + export type CursorWithCountOptions = + | number + | FilterPredicate + | { + includeComments?: boolean; + filter?: FilterPredicate; + count?: number; + }; +} + +declare class SourceCode { + text: string; + ast: SourceCode.Program; + lines: string[]; + hasBOM: boolean; + parserServices: ParserServices; + scopeManager: Scope.ScopeManager; + visitorKeys: SourceCode.VisitorKeys; + tokensAndComments: (TSESTree.Comment | TSESTree.Token)[]; + + constructor(text: string, ast: SourceCode.Program); + constructor(config: SourceCode.Config); + + static splitLines(text: string): string[]; + + getText( + node?: TSESTree.Node, + beforeCount?: number, + afterCount?: number, + ): string; + + getLines(): string[]; + + getAllComments(): TSESTree.Comment[]; + + getComments( + node: TSESTree.Node, + ): { leading: TSESTree.Comment[]; trailing: TSESTree.Comment[] }; + + getJSDocComment(node: TSESTree.Node): TSESTree.Node | TSESTree.Token | null; + + getNodeByRangeIndex(index: number): TSESTree.Node | null; + + isSpaceBetweenTokens(first: TSESTree.Token, second: TSESTree.Token): boolean; + + getLocFromIndex(index: number): TSESTree.LineAndColumnData; + + getIndexFromLoc(location: TSESTree.LineAndColumnData): number; + + // Inherited methods from TokenStore + // --------------------------------- + + getTokenByRangeStart( + offset: number, + options?: { includeComments?: boolean }, + ): TSESTree.Token | null; + + getFirstToken( + node: TSESTree.Node, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getFirstTokens( + node: TSESTree.Node, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getLastToken( + node: TSESTree.Node, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getLastTokens( + node: TSESTree.Node, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokenBefore( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getTokensBefore( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokenAfter( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getTokensAfter( + node: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getFirstTokenBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getFirstTokensBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getLastTokenBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithSkipOptions, + ): TSESTree.Token | null; + + getLastTokensBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + options?: SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokensBetween( + left: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + right: TSESTree.Node | TSESTree.Token | TSESTree.Comment, + padding?: + | number + | SourceCode.FilterPredicate + | SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + getTokens( + node: TSESTree.Node, + beforeCount?: number, + afterCount?: number, + ): TSESTree.Token[]; + getTokens( + node: TSESTree.Node, + options: SourceCode.FilterPredicate | SourceCode.CursorWithCountOptions, + ): TSESTree.Token[]; + + commentsExistBetween( + left: TSESTree.Node | TSESTree.Token, + right: TSESTree.Node | TSESTree.Token, + ): boolean; + + getCommentsBefore( + nodeOrToken: TSESTree.Node | TSESTree.Token, + ): TSESTree.Comment[]; + + getCommentsAfter( + nodeOrToken: TSESTree.Node | TSESTree.Token, + ): TSESTree.Comment[]; + + getCommentsInside(node: TSESTree.Node): TSESTree.Comment[]; +} + +export { SourceCode }; diff --git a/packages/experimental-utils/src/ts-eslint/index.ts b/packages/experimental-utils/src/ts-eslint/index.ts new file mode 100644 index 00000000000..05a5b0b54ad --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/index.ts @@ -0,0 +1,7 @@ +export * from './AST'; +export * from './Linter'; +export * from './ParserOptions'; +export * from './Rule'; +export * from './RuleTester'; +export * from './Scope'; +export * from './SourceCode'; diff --git a/packages/experimental-utils/tests/eslint-utils/applyDefault.test.ts b/packages/experimental-utils/tests/eslint-utils/applyDefault.test.ts new file mode 100644 index 00000000000..3ff4a843ec2 --- /dev/null +++ b/packages/experimental-utils/tests/eslint-utils/applyDefault.test.ts @@ -0,0 +1,58 @@ +import assert from 'assert'; + +import * as util from '../../src/eslint-utils/applyDefault'; + +describe('applyDefault', () => { + it('returns a clone of the default if no options given', () => { + const defaults = [ + { + prop: 'setting', + }, + ]; + const user = null; + const result = util.applyDefault(defaults, user); + + assert.deepStrictEqual(result, defaults); + assert.notStrictEqual(result, defaults); + }); + + it('returns applies a deepMerge to each element in the array', () => { + const defaults = [ + { + prop: 'setting1', + other: 'other', + }, + { + prop: 'setting2', + }, + ] as Record[]; + const user = [ + { + prop: 'new', + other: 'something', + }, + ] as Record[]; + const result = util.applyDefault(defaults, user); + + assert.deepStrictEqual(result, [ + { + prop: 'new', + other: 'something', + }, + { + prop: 'setting2', + }, + ]); + assert.notStrictEqual(result, defaults); + assert.notStrictEqual(result, user); + }); + + it('returns a brand new array', () => { + const defaults: undefined[] = []; + const user: undefined[] = []; + const result = util.applyDefault(defaults, user); + + assert.notStrictEqual(result, defaults); + assert.notStrictEqual(result, user); + }); +}); diff --git a/packages/experimental-utils/tests/eslint-utils/deepMerge.test.ts b/packages/experimental-utils/tests/eslint-utils/deepMerge.test.ts new file mode 100644 index 00000000000..27e55c996a9 --- /dev/null +++ b/packages/experimental-utils/tests/eslint-utils/deepMerge.test.ts @@ -0,0 +1,60 @@ +import assert from 'assert'; + +import * as util from '../../src/eslint-utils/deepMerge'; + +describe('deepMerge', () => { + it('creates a brand new object', () => { + const a = {}; + const b = {}; + const result = util.deepMerge(a, b); + + assert.notStrictEqual(result, a); + assert.notStrictEqual(result, b); + }); + + it('deeply merges objects', () => { + const a = { + stringA1: 'asdf', + numberA1: 1, + boolA1: true, + arrayA1: [1, 2, 3], + objA1: { + stringA2: 'fsda', + numberA2: 2, + boolA2: false, + arrayA2: [3, 2, 1], + objA2: {}, + }, + }; + const b = { + stringB1: 'asdf', + numberB1: 1, + boolB1: true, + arrayB1: [1, 2, 3], + objB1: { + stringB2: 'fsda', + numberB2: 2, + boolB2: false, + arrayB2: [3, 2, 1], + objB2: {}, + }, + }; + + assert.deepStrictEqual(util.deepMerge(a, b), Object.assign({}, a, b)); + }); + + it('deeply overwrites properties in the first one with the second', () => { + const a = { + prop1: { + prop2: 'hi', + }, + }; + const b = { + prop1: { + prop2: 'bye', + }, + }; + + assert.deepStrictEqual(util.deepMerge(a, b), b); + }); +}); diff --git a/packages/experimental-utils/tsconfig.build.json b/packages/experimental-utils/tsconfig.build.json new file mode 100644 index 00000000000..0ce1565b0d0 --- /dev/null +++ b/packages/experimental-utils/tsconfig.build.json @@ -0,0 +1,9 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "rootDir": "./src", + "resolveJsonModule": true + }, + "include": ["src"] +} diff --git a/packages/experimental-utils/tsconfig.json b/packages/experimental-utils/tsconfig.json new file mode 100644 index 00000000000..f469d044ef4 --- /dev/null +++ b/packages/experimental-utils/tsconfig.json @@ -0,0 +1,8 @@ +{ + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist", + "resolveJsonModule": true + }, + "include": ["src", "typings", "tests", "tools"] +} diff --git a/packages/parser/package.json b/packages/parser/package.json index 5beae5aa527..a982469625f 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -38,11 +38,11 @@ }, "dependencies": { "@typescript-eslint/typescript-estree": "1.7.0", + "@typescript-eslint/experimental-utils": "1.7.0", "eslint-scope": "^4.0.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@types/eslint": "^4.16.5", "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/shared-fixtures": "1.7.0" } diff --git a/packages/parser/src/analyze-scope.ts b/packages/parser/src/analyze-scope.ts index 92390dbf0d3..b131104ce87 100644 --- a/packages/parser/src/analyze-scope.ts +++ b/packages/parser/src/analyze-scope.ts @@ -1,16 +1,17 @@ -import { ScopeManager } from './scope/scope-manager'; +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; import { Definition, ParameterDefinition } from 'eslint-scope/lib/definition'; +import { + PatternVisitorCallback, + PatternVisitorOptions, +} from 'eslint-scope/lib/options'; import OriginalPatternVisitor from 'eslint-scope/lib/pattern-visitor'; import Reference from 'eslint-scope/lib/reference'; import OriginalReferencer from 'eslint-scope/lib/referencer'; import { getKeys as fallback } from 'eslint-visitor-keys'; + import { ParserOptions } from './parser-options'; +import { ScopeManager } from './scope/scope-manager'; import { visitorKeys as childVisitorKeys } from './visitor-keys'; -import { - PatternVisitorCallback, - PatternVisitorOptions, -} from 'eslint-scope/lib/options'; -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; /** * Define the override function of `Scope#__define` for global augmentation. diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index fa5aa39dfdc..71478ee2ad6 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -1,14 +1,16 @@ -import traverser from 'eslint/lib/util/traverser'; import { AST_NODE_TYPES, parseAndGenerateServices, - ParserOptions as ParserOptionsTsESTree, + TSESTreeOptions, ParserServices, } from '@typescript-eslint/typescript-estree'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import traverser from 'eslint/lib/util/traverser'; import { analyzeScope } from './analyze-scope'; -import { ParserOptions } from './parser-options'; import { visitorKeys } from './visitor-keys'; +type ParserOptions = TSESLint.ParserOptions; + // note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder const packageJSON = require('../package.json'); @@ -57,7 +59,7 @@ export function parseForESLint( options.ecmaFeatures = {}; } - const parserOptions: ParserOptionsTsESTree = {}; + const parserOptions: TSESTreeOptions = {}; Object.assign(parserOptions, options, { useJSXTextNode: validateBoolean(options.useJSXTextNode, true), jsx: validateBoolean(options.ecmaFeatures.jsx), diff --git a/packages/parser/src/scope/scope-manager.ts b/packages/parser/src/scope/scope-manager.ts index fcdb88175cc..7b7e53c9e84 100644 --- a/packages/parser/src/scope/scope-manager.ts +++ b/packages/parser/src/scope/scope-manager.ts @@ -1,10 +1,9 @@ import { TSESTree } from '@typescript-eslint/typescript-estree'; - import EslintScopeManager, { ScopeManagerOptions, } from 'eslint-scope/lib/scope-manager'; -import { EmptyFunctionScope, EnumScope } from './scopes'; import { Scope } from 'eslint-scope/lib/scope'; +import { EmptyFunctionScope, EnumScope } from './scopes'; /** * based on eslint-scope diff --git a/packages/parser/src/scope/scopes.ts b/packages/parser/src/scope/scopes.ts index f5c27a69f79..9dff225a721 100644 --- a/packages/parser/src/scope/scopes.ts +++ b/packages/parser/src/scope/scopes.ts @@ -1,6 +1,6 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; import { Scope } from 'eslint-scope/lib/scope'; import { ScopeManager } from './scope-manager'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; /** The scope class for enum. */ export class EnumScope extends Scope { diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts index 47d816fe048..c3c205509dd 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -2,6 +2,8 @@ import * as typescriptESTree from '@typescript-eslint/typescript-estree'; import { parse, parseForESLint, Syntax } from '../../src/parser'; import * as scope from '../../src/analyze-scope'; +const { AST_NODE_TYPES } = typescriptESTree; + describe('parser', () => { it('parse() should return just the AST from parseForESLint()', () => { const code = 'const valid = true;'; @@ -60,8 +62,8 @@ describe('parser', () => { }); }); - it('Syntax should contain a frozen object of typescriptESTree.AST_NODE_TYPES', () => { - expect(Syntax).toEqual(typescriptESTree.AST_NODE_TYPES); + it('Syntax should contain a frozen object of AST_NODE_TYPES', () => { + expect(Syntax).toEqual(AST_NODE_TYPES); expect( () => ((Syntax as any).ArrayExpression = 'foo'), ).toThrowErrorMatchingInlineSnapshot( diff --git a/packages/parser/typings/eslint-scope.d.ts b/packages/parser/typings/eslint-scope.d.ts index 62172fd30b7..ec876c63716 100644 --- a/packages/parser/typings/eslint-scope.d.ts +++ b/packages/parser/typings/eslint-scope.d.ts @@ -8,7 +8,7 @@ //----------------------------------------------------------------------- declare module 'eslint-scope/lib/options' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; export type PatternVisitorCallback = ( pattern: TSESTree.Identifier, info: { @@ -31,7 +31,7 @@ declare module 'eslint-scope/lib/options' { } declare module 'eslint-scope/lib/variable' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; import Reference from 'eslint-scope/lib/reference'; import { Definition } from 'eslint-scope/lib/definition'; @@ -45,7 +45,7 @@ declare module 'eslint-scope/lib/variable' { } declare module 'eslint-scope/lib/definition' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; export class Definition { type: string; @@ -78,7 +78,7 @@ declare module 'eslint-scope/lib/definition' { declare module 'eslint-scope/lib/pattern-visitor' { import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; import { PatternVisitorCallback, PatternVisitorOptions, @@ -115,7 +115,7 @@ declare module 'eslint-scope/lib/pattern-visitor' { declare module 'eslint-scope/lib/referencer' { import { Scope } from 'eslint-scope/lib/scope'; import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; import { PatternVisitorCallback, PatternVisitorOptions, @@ -192,7 +192,7 @@ declare module 'eslint-scope/lib/referencer' { } declare module 'eslint-scope/lib/scope' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; import Reference from 'eslint-scope/lib/reference'; import Variable from 'eslint-scope/lib/variable'; import ScopeManager from 'eslint-scope/lib/scope-manager'; @@ -378,7 +378,7 @@ declare module 'eslint-scope/lib/scope' { } declare module 'eslint-scope/lib/reference' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; import { Scope } from 'eslint-scope/lib/scope'; import Variable from 'eslint-scope/lib/variable'; @@ -402,7 +402,7 @@ declare module 'eslint-scope/lib/reference' { } declare module 'eslint-scope/lib/scope-manager' { - import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { TSESTree } from '@typescript-eslint/experimental-utils'; import { Scope } from 'eslint-scope/lib/scope'; import Variable from 'eslint-scope/lib/variable'; diff --git a/packages/typescript-estree/src/ast-converter.ts b/packages/typescript-estree/src/ast-converter.ts index 72ff381f87e..25d291dee4c 100644 --- a/packages/typescript-estree/src/ast-converter.ts +++ b/packages/typescript-estree/src/ast-converter.ts @@ -1,11 +1,11 @@ +import { SourceFile } from 'typescript'; import { convertError, Converter } from './convert'; import { convertComments } from './convert-comments'; import { convertTokens } from './node-utils'; -import ts from 'typescript'; import { Extra } from './parser-options'; export default function astConverter( - ast: ts.SourceFile, + ast: SourceFile, extra: Extra, shouldPreserveNodeMaps: boolean, ) { diff --git a/packages/typescript-estree/src/convert-comments.ts b/packages/typescript-estree/src/convert-comments.ts index 5474a71d66d..026500da7d3 100644 --- a/packages/typescript-estree/src/convert-comments.ts +++ b/packages/typescript-estree/src/convert-comments.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { getLocFor, getNodeContainer } from './node-utils'; import { TSESTree } from './ts-estree'; diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index 1c70dbf8642..e0e535c774a 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { canContainDirective, createError, @@ -18,8 +18,7 @@ import { isOptional, unescapeStringLiteralText, } from './node-utils'; -import { AST_NODE_TYPES, TSESTree } from './ts-estree'; -import { TSNode } from './ts-nodes'; +import { AST_NODE_TYPES, TSESTree, TSNode } from './ts-estree'; const SyntaxKind = ts.SyntaxKind; diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index 80793df4e2d..d682bb091b4 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -1,5 +1,5 @@ -import ts from 'typescript'; import unescape from 'lodash.unescape'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from './ts-estree'; const SyntaxKind = ts.SyntaxKind; diff --git a/packages/typescript-estree/src/parser-options.ts b/packages/typescript-estree/src/parser-options.ts index 5d1821bd50c..585effea268 100644 --- a/packages/typescript-estree/src/parser-options.ts +++ b/packages/typescript-estree/src/parser-options.ts @@ -1,17 +1,16 @@ import { Program } from 'typescript'; -import { Token, Comment, Node } from './ts-estree/ts-estree'; -import { TSNode } from './ts-nodes'; +import { TSESTree, TSNode } from './ts-estree'; export interface Extra { errorOnUnknownASTType: boolean; errorOnTypeScriptSyntacticAndSemanticIssues: boolean; useJSXTextNode: boolean; - tokens: null | Token[]; + tokens: null | TSESTree.Token[]; comment: boolean; code: string; range: boolean; loc: boolean; - comments: Comment[]; + comments: TSESTree.Comment[]; strict: boolean; jsx: boolean; log: Function; @@ -21,7 +20,7 @@ export interface Extra { preserveNodeMaps?: boolean; } -export interface ParserOptions { +export interface TSESTreeOptions { range?: boolean; loc?: boolean; tokens?: boolean; @@ -38,6 +37,8 @@ export interface ParserOptions { preserveNodeMaps?: boolean; } +// This lets us use generics to type the return value, and removes the need to +// handle the undefined type in the get method export interface ParserWeakMap { get(key: TKey): TValue; has(key: any): boolean; @@ -45,6 +46,6 @@ export interface ParserWeakMap { export interface ParserServices { program: Program | undefined; - esTreeNodeToTSNodeMap: ParserWeakMap | undefined; - tsNodeToESTreeNodeMap: ParserWeakMap | undefined; + esTreeNodeToTSNodeMap: ParserWeakMap | undefined; + tsNodeToESTreeNodeMap: ParserWeakMap | undefined; } diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 2f4003c8e3d..1a963859d96 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,15 +1,15 @@ -import { - calculateProjectParserOptions, - createProgram, -} from './tsconfig-parser'; import semver from 'semver'; -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import convert from './ast-converter'; import { convertError } from './convert'; import { firstDefined } from './node-utils'; -import { TSESTree } from './ts-estree'; -import { Extra, ParserOptions, ParserServices } from './parser-options'; +import { Extra, TSESTreeOptions, ParserServices } from './parser-options'; import { getFirstSemanticOrSyntacticError } from './semantic-errors'; +import { TSESTree } from './ts-estree'; +import { + calculateProjectParserOptions, + createProgram, +} from './tsconfig-parser'; /** * This needs to be kept in sync with the top-level README.md in the @@ -66,7 +66,7 @@ function resetExtra(): void { * @param options The config object * @returns If found, returns the source file corresponding to the code and the containing program */ -function getASTFromProject(code: string, options: ParserOptions) { +function getASTFromProject(code: string, options: TSESTreeOptions) { return firstDefined( calculateProjectParserOptions( code, @@ -87,7 +87,7 @@ function getASTFromProject(code: string, options: ParserOptions) { * @param options The config object * @returns If found, returns the source file corresponding to the code and the containing program */ -function getASTAndDefaultProject(code: string, options: ParserOptions) { +function getASTAndDefaultProject(code: string, options: TSESTreeOptions) { const fileName = options.filePath || getFileName(options); const program = createProgram(code, fileName, extra); const ast = program && program.getSourceFile(fileName); @@ -159,7 +159,7 @@ function createNewProgram(code: string) { */ function getProgramAndAST( code: string, - options: ParserOptions, + options: TSESTreeOptions, shouldProvideParserServices: boolean, ) { return ( @@ -169,7 +169,7 @@ function getProgramAndAST( ); } -function applyParserOptionsToExtra(options: ParserOptions): void { +function applyParserOptionsToExtra(options: TSESTreeOptions): void { /** * Track range information in the AST */ @@ -277,12 +277,12 @@ function warnAboutTSVersion(): void { // Parser //------------------------------------------------------------------------------ -type AST = TSESTree.Program & +type AST = TSESTree.Program & (T['range'] extends true ? { range: [number, number] } : {}) & (T['tokens'] extends true ? { tokens: TSESTree.Token[] } : {}) & (T['comment'] extends true ? { comments: TSESTree.Comment[] } : {}); -export interface ParseAndGenerateServicesResult { +export interface ParseAndGenerateServicesResult { ast: AST; services: ParserServices; } @@ -293,7 +293,7 @@ export interface ParseAndGenerateServicesResult { export const version: string = require('../package.json').version; -export function parse( +export function parse( code: string, options?: T, ): AST { @@ -344,7 +344,7 @@ export function parse( } export function parseAndGenerateServices< - T extends ParserOptions = ParserOptions + T extends TSESTreeOptions = TSESTreeOptions >(code: string, options: T): ParseAndGenerateServicesResult { /** * Reset the parse configuration @@ -427,5 +427,5 @@ export function parseAndGenerateServices< }; } -export { AST_NODE_TYPES, AST_TOKEN_TYPES, TSESTree } from './ts-estree'; -export { ParserOptions, ParserServices }; +export { TSESTreeOptions, ParserServices }; +export * from './ts-estree'; diff --git a/packages/typescript-estree/src/semantic-errors.ts b/packages/typescript-estree/src/semantic-errors.ts index 14568aba123..f31eb634068 100644 --- a/packages/typescript-estree/src/semantic-errors.ts +++ b/packages/typescript-estree/src/semantic-errors.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports interface SemanticOrSyntacticError extends ts.Diagnostic { message: string; diff --git a/packages/typescript-estree/src/ts-estree/index.ts b/packages/typescript-estree/src/ts-estree/index.ts index 4a6b74aef1f..5bed681f209 100644 --- a/packages/typescript-estree/src/ts-estree/index.ts +++ b/packages/typescript-estree/src/ts-estree/index.ts @@ -1,5 +1,5 @@ import * as TSESTree from './ts-estree'; -export * from './ast-node-types'; -export * from '../ts-nodes'; export { TSESTree }; +export * from './ast-node-types'; +export * from './ts-nodes'; diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index 7043cafa046..ec7640b56b2 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -70,7 +70,7 @@ export type OptionalRangeAndLoc = Pick< T, Exclude > & { - range?: [number, number]; + range?: Range; loc?: SourceLocation; }; diff --git a/packages/typescript-estree/src/ts-nodes.ts b/packages/typescript-estree/src/ts-estree/ts-nodes.ts similarity index 97% rename from packages/typescript-estree/src/ts-nodes.ts rename to packages/typescript-estree/src/ts-estree/ts-nodes.ts index b917584cf38..b4298fa1530 100644 --- a/packages/typescript-estree/src/ts-nodes.ts +++ b/packages/typescript-estree/src/ts-estree/ts-nodes.ts @@ -1,4 +1,4 @@ -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports export type TSNode = ts.Node & ( diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 0f4a9b7c35e..44e1f13b28b 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -1,5 +1,5 @@ import path from 'path'; -import ts from 'typescript'; +import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports import { Extra } from './parser-options'; //------------------------------------------------------------------------------ diff --git a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts index f30b35c3283..4b8e05d166b 100644 --- a/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts +++ b/packages/typescript-estree/tests/ast-alignment/fixtures-to-test.ts @@ -1,5 +1,5 @@ -import glob from 'glob'; import fs from 'fs'; +import glob from 'glob'; import path from 'path'; import jsxKnownIssues from '../../../shared-fixtures/jsx-known-issues'; diff --git a/packages/typescript-estree/tests/ast-alignment/parse.ts b/packages/typescript-estree/tests/ast-alignment/parse.ts index 6113f4ab5b8..84fb70f5b81 100644 --- a/packages/typescript-estree/tests/ast-alignment/parse.ts +++ b/packages/typescript-estree/tests/ast-alignment/parse.ts @@ -1,7 +1,7 @@ +import { ParserPlugin } from '@babel/parser'; import codeFrame from 'babel-code-frame'; import * as parser from '../../src/parser'; import * as parseUtils from './utils'; -import { ParserPlugin } from '@babel/parser'; function createError(message: string, line: number, column: number) { // Construct an error similar to the ones thrown by Babylon. diff --git a/packages/typescript-estree/tests/ast-alignment/utils.ts b/packages/typescript-estree/tests/ast-alignment/utils.ts index f661245be7f..58a7926ca9a 100644 --- a/packages/typescript-estree/tests/ast-alignment/utils.ts +++ b/packages/typescript-estree/tests/ast-alignment/utils.ts @@ -1,5 +1,5 @@ -import isPlainObject from 'lodash.isplainobject'; import { AST_NODE_TYPES } from '../../src/ts-estree'; +import isPlainObject from 'lodash.isplainobject'; /** * By default, pretty-format (within Jest matchers) retains the names/types of nodes from the babylon AST, diff --git a/packages/typescript-estree/tests/lib/comments.ts b/packages/typescript-estree/tests/lib/comments.ts index 7371388acdc..68f9cd800d3 100644 --- a/packages/typescript-estree/tests/lib/comments.ts +++ b/packages/typescript-estree/tests/lib/comments.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname } from 'path'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -16,7 +16,7 @@ describe('Comments', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/javascript.ts b/packages/typescript-estree/tests/lib/javascript.ts index 0d12ac4cc13..1cce991c0f4 100644 --- a/packages/typescript-estree/tests/lib/javascript.ts +++ b/packages/typescript-estree/tests/lib/javascript.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'fs'; import glob from 'glob'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -13,7 +13,7 @@ const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.js`); describe('javascript', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/jsx.ts b/packages/typescript-estree/tests/lib/jsx.ts index 39da5735a8a..52b6debdf6b 100644 --- a/packages/typescript-estree/tests/lib/jsx.ts +++ b/packages/typescript-estree/tests/lib/jsx.ts @@ -1,6 +1,6 @@ import { readFileSync } from 'fs'; import glob from 'glob'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -29,7 +29,7 @@ describe('JSX', () => { ): (filename: string) => void { return filename => { const code = readFileSync(filename, 'utf8'); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index c757bad7cde..3c34f705556 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -1,6 +1,6 @@ import * as parser from '../../src/parser'; import * as astConverter from '../../src/ast-converter'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock } from '../../tools/test-utils'; describe('parse()', () => { @@ -23,7 +23,7 @@ describe('parse()', () => { describe('general', () => { const code = 'let foo = bar;'; - const config: ParserOptions = { + const config: TSESTreeOptions = { comment: true, tokens: true, range: true, @@ -38,7 +38,7 @@ describe('parse()', () => { describe('non string code', () => { const code = (12345 as any) as string; - const config: ParserOptions = { + const config: TSESTreeOptions = { comment: true, tokens: true, range: true, @@ -97,7 +97,7 @@ describe('parse()', () => { describe('errorOnTypeScriptSyntacticAndSemanticIssues', () => { const code = '@test const foo = 2'; - const options: ParserOptions = { + const options: TSESTreeOptions = { comment: true, tokens: true, range: true, @@ -130,7 +130,7 @@ describe('parse()', () => { describe('preserveNodeMaps', () => { const code = 'var a = true'; - const baseConfig: ParserOptions = { + const baseConfig: TSESTreeOptions = { comment: true, tokens: true, range: true, diff --git a/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts b/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts index bfae9601a14..fc05b04ec58 100644 --- a/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts +++ b/packages/typescript-estree/tests/lib/semantic-diagnostics-enabled.ts @@ -16,7 +16,7 @@ describe('Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: parser.ParserOptions = { + const config: parser.TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/semanticInfo.ts b/packages/typescript-estree/tests/lib/semanticInfo.ts index df3fed691a4..189816f08c6 100644 --- a/packages/typescript-estree/tests/lib/semanticInfo.ts +++ b/packages/typescript-estree/tests/lib/semanticInfo.ts @@ -2,23 +2,19 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname, join, resolve } from 'path'; import ts from 'typescript'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, parseCodeAndGenerateServices, } from '../../tools/test-utils'; import { parseAndGenerateServices } from '../../src/parser'; -import { - VariableDeclaration, - ClassDeclaration, - ClassProperty, -} from '../../src/ts-estree/ts-estree'; +import { TSESTree } from '../../src/ts-estree'; const FIXTURES_DIR = './tests/fixtures/semanticInfo'; const testFiles = glob.sync(`${FIXTURES_DIR}/**/*.src.ts`); -function createOptions(fileName: string): ParserOptions & { cwd?: string } { +function createOptions(fileName: string): TSESTreeOptions & { cwd?: string } { return { loc: true, range: true, @@ -144,15 +140,16 @@ describe('semanticInfo', () => { ); expect(parseResult).toHaveProperty('services.esTreeNodeToTSNodeMap'); - const binaryExpression = (parseResult.ast.body[0] as VariableDeclaration) - .declarations[0].init!; + const binaryExpression = (parseResult.ast + .body[0] as TSESTree.VariableDeclaration).declarations[0].init!; const tsBinaryExpression = parseResult.services.esTreeNodeToTSNodeMap!.get( binaryExpression, ); expect(tsBinaryExpression.kind).toEqual(ts.SyntaxKind.BinaryExpression); const computedPropertyString = ((parseResult.ast - .body[1] as ClassDeclaration).body.body[0] as ClassProperty).key; + .body[1] as TSESTree.ClassDeclaration).body + .body[0] as TSESTree.ClassProperty).key; const tsComputedPropertyString = parseResult.services.esTreeNodeToTSNodeMap!.get( computedPropertyString, ); diff --git a/packages/typescript-estree/tests/lib/tsx.ts b/packages/typescript-estree/tests/lib/tsx.ts index e9fbdad600d..84160d2b4b8 100644 --- a/packages/typescript-estree/tests/lib/tsx.ts +++ b/packages/typescript-estree/tests/lib/tsx.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname } from 'path'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -16,7 +16,7 @@ describe('TSX', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tests/lib/typescript.ts b/packages/typescript-estree/tests/lib/typescript.ts index d11136dfdae..2e5e70b04cf 100644 --- a/packages/typescript-estree/tests/lib/typescript.ts +++ b/packages/typescript-estree/tests/lib/typescript.ts @@ -1,7 +1,7 @@ import { readFileSync } from 'fs'; import glob from 'glob'; import { extname } from 'path'; -import { ParserOptions } from '../../src/parser-options'; +import { TSESTreeOptions } from '../../src/parser-options'; import { createSnapshotTestBlock, formatSnapshotName, @@ -16,7 +16,7 @@ describe('typescript', () => { testFiles.forEach(filename => { const code = readFileSync(filename, 'utf8'); const fileExtension = extname(filename); - const config: ParserOptions = { + const config: TSESTreeOptions = { loc: true, range: true, tokens: true, diff --git a/packages/typescript-estree/tools/test-utils.ts b/packages/typescript-estree/tools/test-utils.ts index 4fd54335ebc..f1c587df237 100644 --- a/packages/typescript-estree/tools/test-utils.ts +++ b/packages/typescript-estree/tools/test-utils.ts @@ -1,5 +1,5 @@ import * as parser from '../src/parser'; -import { ParserOptions } from '../src/parser-options'; +import { TSESTreeOptions } from '../src/parser-options'; /** * Returns a raw copy of the given AST @@ -19,7 +19,7 @@ export function getRaw(ast: any) { export function parseCodeAndGenerateServices( code: string, - config: ParserOptions, + config: TSESTreeOptions, ) { return parser.parseAndGenerateServices(code, config); } @@ -28,13 +28,13 @@ export function parseCodeAndGenerateServices( * Returns a function which can be used as the callback of a Jest test() block, * and which performs an assertion on the snapshot for the given code and config. * @param {string} code The source code to parse - * @param {ParserOptions} config the parser configuration + * @param {TSESTreeOptions} config the parser configuration * @param {boolean} generateServices Flag determining whether to generate ast maps and program or not * @returns {jest.ProvidesCallback} callback for Jest it() block */ export function createSnapshotTestBlock( code: string, - config: ParserOptions, + config: TSESTreeOptions, generateServices?: true, ) { /** diff --git a/packages/typescript-estree/tsconfig.build.json b/packages/typescript-estree/tsconfig.build.json index b0fced27d72..792172fb82f 100644 --- a/packages/typescript-estree/tsconfig.build.json +++ b/packages/typescript-estree/tsconfig.build.json @@ -1,7 +1,8 @@ { "extends": "../../tsconfig.base.json", "compilerOptions": { - "outDir": "./dist" + "outDir": "./dist", + "rootDir": "./src" }, "include": ["src"] } diff --git a/packages/typescript-estree/tsconfig.json b/packages/typescript-estree/tsconfig.json index 1fdde9ad21c..e389d7edef3 100644 --- a/packages/typescript-estree/tsconfig.json +++ b/packages/typescript-estree/tsconfig.json @@ -1,4 +1,7 @@ { - "extends": "./tsconfig.build.json", + "extends": "../../tsconfig.base.json", + "compilerOptions": { + "outDir": "./dist" + }, "include": ["src", "tests", "tools"] } diff --git a/yarn.lock b/yarn.lock index ddecb6a80ef..b296e145ad2 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1187,7 +1187,7 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/eslint@^4.16.3", "@types/eslint@^4.16.5": +"@types/eslint@^4.16.3": version "4.16.6" resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-4.16.6.tgz#96d4ecddbea618ab0b55eaf0dffedf387129b06c" integrity sha512-GL7tGJig55FeclpOytU7nCCqtR143jBoC7AUdH0DO9xBSIFiNNUFCY/S3KNWsHeQJuU3hjw/OC1+kRTFNXqUZQ== From eb613ca451624c3f9db280cab195f2a565d5de88 Mon Sep 17 00:00:00 2001 From: James Henry Date: Fri, 10 May 2019 12:53:33 -0400 Subject: [PATCH 10/41] chore: publish v1.8.0 --- CHANGELOG.md | 28 ++++++++++++++++++++++ lerna.json | 2 +- packages/eslint-plugin-tslint/CHANGELOG.md | 7 ++++++ packages/eslint-plugin-tslint/package.json | 4 ++-- packages/eslint-plugin/CHANGELOG.md | 25 +++++++++++++++++++ packages/eslint-plugin/package.json | 6 ++--- packages/experimental-utils/CHANGELOG.md | 10 ++++++++ packages/experimental-utils/package.json | 5 ++-- packages/parser/CHANGELOG.md | 11 +++++++++ packages/parser/package.json | 8 +++---- packages/shared-fixtures/CHANGELOG.md | 8 +++++++ packages/shared-fixtures/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 15 ++++++++++++ packages/typescript-estree/package.json | 4 ++-- 14 files changed, 119 insertions(+), 16 deletions(-) create mode 100644 packages/experimental-utils/CHANGELOG.md diff --git a/CHANGELOG.md b/CHANGELOG.md index f21399e8351..cc51d8504d0 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,34 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- **eslint-plugin:** [array-type] support readonly operator ([#429](https://github.com/typescript-eslint/typescript-eslint/issues/429)) ([8e2d2f5](https://github.com/typescript-eslint/typescript-eslint/commit/8e2d2f5)) +- **eslint-plugin:** [explicit-function-return-type] Add handling for class properties ([#502](https://github.com/typescript-eslint/typescript-eslint/issues/502)) ([2c36325](https://github.com/typescript-eslint/typescript-eslint/commit/2c36325)) +- **eslint-plugin:** [no-extra-parens] Fix build error ([298d66c](https://github.com/typescript-eslint/typescript-eslint/commit/298d66c)) +- **eslint-plugin:** [unbound-method] Work around class prototype bug ([#499](https://github.com/typescript-eslint/typescript-eslint/issues/499)) ([3219aa7](https://github.com/typescript-eslint/typescript-eslint/commit/3219aa7)) +- **eslint-plugin:** correct eslint-recommended settings ([d52a683](https://github.com/typescript-eslint/typescript-eslint/commit/d52a683)) +- **eslint-plugin:** explicit-func-return-type: support object types and as expressions ([#459](https://github.com/typescript-eslint/typescript-eslint/issues/459)) ([d19e512](https://github.com/typescript-eslint/typescript-eslint/commit/d19e512)) +- **eslint-plugin:** restrict-plus-operands: generic constraint support ([#440](https://github.com/typescript-eslint/typescript-eslint/issues/440)) ([3f305b1](https://github.com/typescript-eslint/typescript-eslint/commit/3f305b1)) +- upgrade lockfile versions ([#487](https://github.com/typescript-eslint/typescript-eslint/issues/487)) ([f029dba](https://github.com/typescript-eslint/typescript-eslint/commit/f029dba)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) +- **eslint-plugin:** support switch statement [unbound-method](<[#485](https://github.com/typescript-eslint/typescript-eslint/issues/485)>) ([e99ca81](https://github.com/typescript-eslint/typescript-eslint/commit/e99ca81)) +- **typescript-estree:** ensure parents are defined during subsequent parses ([#500](https://github.com/typescript-eslint/typescript-eslint/issues/500)) ([665278f](https://github.com/typescript-eslint/typescript-eslint/commit/665278f)) + +### Features + +- **eslint-plugin:** (EXPERIMENTAL) begin indent rewrite ([#439](https://github.com/typescript-eslint/typescript-eslint/issues/439)) ([6eb97d4](https://github.com/typescript-eslint/typescript-eslint/commit/6eb97d4)) +- **eslint-plugin:** Add better non-null handling [no-unnecessary-type-assertion](<[#478](https://github.com/typescript-eslint/typescript-eslint/issues/478)>) ([4cd5590](https://github.com/typescript-eslint/typescript-eslint/commit/4cd5590)) +- **eslint-plugin:** Add func-call-spacing ([#448](https://github.com/typescript-eslint/typescript-eslint/issues/448)) ([92e65ec](https://github.com/typescript-eslint/typescript-eslint/commit/92e65ec)) +- **eslint-plugin:** Add new config "eslint-recommended" ([#488](https://github.com/typescript-eslint/typescript-eslint/issues/488)) ([2600a9f](https://github.com/typescript-eslint/typescript-eslint/commit/2600a9f)) +- **eslint-plugin:** add no-magic-numbers rule ([#373](https://github.com/typescript-eslint/typescript-eslint/issues/373)) ([43fa09c](https://github.com/typescript-eslint/typescript-eslint/commit/43fa09c)) +- **eslint-plugin:** Add semi [extension](<[#461](https://github.com/typescript-eslint/typescript-eslint/issues/461)>) ([0962017](https://github.com/typescript-eslint/typescript-eslint/commit/0962017)) +- **eslint-plugin:** no-inferrable-types: Support more primitives ([#442](https://github.com/typescript-eslint/typescript-eslint/issues/442)) ([4e193ca](https://github.com/typescript-eslint/typescript-eslint/commit/4e193ca)) +- **ts-estree:** add preserveNodeMaps option ([#494](https://github.com/typescript-eslint/typescript-eslint/issues/494)) ([c3061f9](https://github.com/typescript-eslint/typescript-eslint/commit/c3061f9)) +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 9bcec435d8f..3b33436bfbf 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.7.0", + "version": "1.8.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 6cdd0814015..d78fa383f77 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,13 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- upgrade lockfile versions ([#487](https://github.com/typescript-eslint/typescript-eslint/issues/487)) ([f029dba](https://github.com/typescript-eslint/typescript-eslint/commit/f029dba)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) **Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 72eb598c64a..f57aabccddf 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "1.7.0", + "version": "1.8.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -36,6 +36,6 @@ "devDependencies": { "@types/eslint": "^4.16.3", "@types/lodash.memoize": "^4.1.4", - "@typescript-eslint/parser": "1.7.0" + "@typescript-eslint/parser": "1.8.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 97c3b7e7b7d..f33b4709976 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,31 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- **eslint-plugin:** [array-type] support readonly operator ([#429](https://github.com/typescript-eslint/typescript-eslint/issues/429)) ([8e2d2f5](https://github.com/typescript-eslint/typescript-eslint/commit/8e2d2f5)) +- **eslint-plugin:** [explicit-function-return-type] Add handling for class properties ([#502](https://github.com/typescript-eslint/typescript-eslint/issues/502)) ([2c36325](https://github.com/typescript-eslint/typescript-eslint/commit/2c36325)) +- **eslint-plugin:** [no-extra-parens] Fix build error ([298d66c](https://github.com/typescript-eslint/typescript-eslint/commit/298d66c)) +- **eslint-plugin:** [unbound-method] Work around class prototype bug ([#499](https://github.com/typescript-eslint/typescript-eslint/issues/499)) ([3219aa7](https://github.com/typescript-eslint/typescript-eslint/commit/3219aa7)) +- **eslint-plugin:** correct eslint-recommended settings ([d52a683](https://github.com/typescript-eslint/typescript-eslint/commit/d52a683)) +- **eslint-plugin:** explicit-func-return-type: support object types and as expressions ([#459](https://github.com/typescript-eslint/typescript-eslint/issues/459)) ([d19e512](https://github.com/typescript-eslint/typescript-eslint/commit/d19e512)) +- **eslint-plugin:** restrict-plus-operands: generic constraint support ([#440](https://github.com/typescript-eslint/typescript-eslint/issues/440)) ([3f305b1](https://github.com/typescript-eslint/typescript-eslint/commit/3f305b1)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) +- **eslint-plugin:** support switch statement [unbound-method](<[#485](https://github.com/typescript-eslint/typescript-eslint/issues/485)>) ([e99ca81](https://github.com/typescript-eslint/typescript-eslint/commit/e99ca81)) + +### Features + +- **eslint-plugin:** (EXPERIMENTAL) begin indent rewrite ([#439](https://github.com/typescript-eslint/typescript-eslint/issues/439)) ([6eb97d4](https://github.com/typescript-eslint/typescript-eslint/commit/6eb97d4)) +- **eslint-plugin:** Add better non-null handling [no-unnecessary-type-assertion](<[#478](https://github.com/typescript-eslint/typescript-eslint/issues/478)>) ([4cd5590](https://github.com/typescript-eslint/typescript-eslint/commit/4cd5590)) +- **eslint-plugin:** Add func-call-spacing ([#448](https://github.com/typescript-eslint/typescript-eslint/issues/448)) ([92e65ec](https://github.com/typescript-eslint/typescript-eslint/commit/92e65ec)) +- **eslint-plugin:** Add new config "eslint-recommended" ([#488](https://github.com/typescript-eslint/typescript-eslint/issues/488)) ([2600a9f](https://github.com/typescript-eslint/typescript-eslint/commit/2600a9f)) +- **eslint-plugin:** add no-magic-numbers rule ([#373](https://github.com/typescript-eslint/typescript-eslint/issues/373)) ([43fa09c](https://github.com/typescript-eslint/typescript-eslint/commit/43fa09c)) +- **eslint-plugin:** Add semi [extension](<[#461](https://github.com/typescript-eslint/typescript-eslint/issues/461)>) ([0962017](https://github.com/typescript-eslint/typescript-eslint/commit/0962017)) +- **eslint-plugin:** no-inferrable-types: Support more primitives ([#442](https://github.com/typescript-eslint/typescript-eslint/issues/442)) ([4e193ca](https://github.com/typescript-eslint/typescript-eslint/commit/4e193ca)) +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) ### Bug Fixes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index ae2f544fa93..53d21ecf3da 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "1.7.0", + "version": "1.8.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -36,8 +36,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/parser": "1.7.0", - "@typescript-eslint/experimental-utils": "1.7.0", + "@typescript-eslint/experimental-utils": "1.8.0", + "@typescript-eslint/parser": "1.8.0", "eslint-utils": "^1.3.1", "regexpp": "^2.0.1", "requireindex": "^1.2.0", diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md new file mode 100644 index 00000000000..dfb21f270f9 --- /dev/null +++ b/packages/experimental-utils/CHANGELOG.md @@ -0,0 +1,10 @@ +# Change Log + +All notable changes to this project will be documented in this file. +See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. + +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Features + +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index d1ab0c20078..03f541c8b1b 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "1.7.0", + "version": "1.8.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -31,9 +31,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.7.0" + "@typescript-eslint/typescript-estree": "1.8.0" }, - "devDependencies": {}, "peerDependencies": { "typescript": "*" } diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 6fc65257e15..9d5e114267f 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- upgrade lockfile versions ([#487](https://github.com/typescript-eslint/typescript-eslint/issues/487)) ([f029dba](https://github.com/typescript-eslint/typescript-eslint/commit/f029dba)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) + +### Features + +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) **Note:** Version bump only for package @typescript-eslint/parser diff --git a/packages/parser/package.json b/packages/parser/package.json index a982469625f..2b64f359e2c 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "1.7.0", + "version": "1.8.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "files": [ @@ -37,13 +37,13 @@ "eslint": "^5.0.0" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.7.0", - "@typescript-eslint/experimental-utils": "1.7.0", + "@typescript-eslint/experimental-utils": "1.8.0", + "@typescript-eslint/typescript-estree": "1.8.0", "eslint-scope": "^4.0.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/shared-fixtures": "1.7.0" + "@typescript-eslint/shared-fixtures": "1.8.0" } } diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 32bdb51cae3..18c73567c6e 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) **Note:** Version bump only for package @typescript-eslint/shared-fixtures diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 7ea47d8b5a4..1d66074de06 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "1.7.0", + "version": "1.8.0", "private": true } diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 936720f36d5..88e97d5f779 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,21 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) + +### Bug Fixes + +- **eslint-plugin:** [array-type] support readonly operator ([#429](https://github.com/typescript-eslint/typescript-eslint/issues/429)) ([8e2d2f5](https://github.com/typescript-eslint/typescript-eslint/commit/8e2d2f5)) +- **eslint-plugin:** Support more nodes [no-extra-parens](<[#465](https://github.com/typescript-eslint/typescript-eslint/issues/465)>) ([2d15644](https://github.com/typescript-eslint/typescript-eslint/commit/2d15644)) +- **typescript-estree:** ensure parents are defined during subsequent parses ([#500](https://github.com/typescript-eslint/typescript-eslint/issues/500)) ([665278f](https://github.com/typescript-eslint/typescript-eslint/commit/665278f)) + +### Features + +- **eslint-plugin:** (EXPERIMENTAL) begin indent rewrite ([#439](https://github.com/typescript-eslint/typescript-eslint/issues/439)) ([6eb97d4](https://github.com/typescript-eslint/typescript-eslint/commit/6eb97d4)) +- **eslint-plugin:** no-inferrable-types: Support more primitives ([#442](https://github.com/typescript-eslint/typescript-eslint/issues/442)) ([4e193ca](https://github.com/typescript-eslint/typescript-eslint/commit/4e193ca)) +- **ts-estree:** add preserveNodeMaps option ([#494](https://github.com/typescript-eslint/typescript-eslint/issues/494)) ([c3061f9](https://github.com/typescript-eslint/typescript-eslint/commit/c3061f9)) +- Move shared types into their own package ([#425](https://github.com/typescript-eslint/typescript-eslint/issues/425)) ([a7a03ce](https://github.com/typescript-eslint/typescript-eslint/commit/a7a03ce)) + # [1.7.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.6.0...v1.7.0) (2019-04-20) ### Features diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 3a57c313925..3a3312a0708 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "1.7.0", + "version": "1.8.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -42,6 +42,6 @@ }, "devDependencies": { "@babel/types": "^7.3.2", - "@typescript-eslint/shared-fixtures": "1.7.0" + "@typescript-eslint/shared-fixtures": "1.8.0" } } From f61d421c968717b2bc55cf7971c05a144b28f715 Mon Sep 17 00:00:00 2001 From: Ricky Lippmann <3674067+ldrick@users.noreply.github.com> Date: Sat, 11 May 2019 01:55:49 +0200 Subject: [PATCH 11/41] feat(eslint-plugin): add prefer-regexp-exec rule (#305) --- packages/eslint-plugin/README.md | 1 + .../docs/rules/prefer-regexp-exec.md | 53 ++++++ .../src/rules/prefer-regexp-exec.ts | 66 +++++++ .../rules/prefer-string-starts-ends-with.ts | 57 +----- packages/eslint-plugin/src/util/types.ts | 57 ++++++ .../tests/rules/prefer-regexp-exec.test.ts | 180 ++++++++++++++++++ 6 files changed, 361 insertions(+), 53 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/prefer-regexp-exec.md create mode 100644 packages/eslint-plugin/src/rules/prefer-regexp-exec.ts create mode 100644 packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index b296291f4ae..22afc3659ac 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -162,6 +162,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | | [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | :heavy_check_mark: | | :thought_balloon: | +| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Enforce to use `RegExp#exec` over `String#match` | | | :thought_balloon: | | [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one. (`unified-signatures` from TSLint) | | | | diff --git a/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md b/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md new file mode 100644 index 00000000000..282907851b8 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-regexp-exec.md @@ -0,0 +1,53 @@ +# Enforce to use `RegExp#exec` over `String#match` (prefer-regexp-exec) + +`RegExp#exec` is faster than `String#match` and both work the same when not using the `/g` flag. + +## Rule Details + +This rule is aimed at enforcing the more performant way of applying regular expressions on strings. + +From [`String#match` on MDN](https://developer.mozilla.org/en-US/docs/Web/JavaScript/Reference/Global_Objects/String/match): + +> If the regular expression does not include the g flag, returns the same result as RegExp.exec(). + +From [Stack Overflow](https://stackoverflow.com/questions/9214754/what-is-the-difference-between-regexp-s-exec-function-and-string-s-match-fun) + +> `RegExp.prototype.exec` is a lot faster than `String.prototype.match`, but that’s because they are not exactly the same thing, they are different. + +Examples of **incorrect** code for this rule: + +```ts +'something'.match(/thing/); + +'some things are just things'.match(/thing/); + +const text = 'something'; +const search = /thing/; +text.match(search); +``` + +Examples of **correct** code for this rule: + +```ts +/thing/.exec('something'); + +'some things are just things'.match(/thing/g); + +const text = 'something'; +const search = /thing/; +search.exec(text); +``` + +## Options + +There are no options. + +```json +{ + "@typescript-eslint/prefer-regexp-exec": "error" +} +``` + +## When Not To Use It + +If you prefer consistent use of `String#match` for both, with `g` flag and without it, you can turn this rule off. diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts new file mode 100644 index 00000000000..ea4e6742d05 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -0,0 +1,66 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { createRule, getParserServices, getTypeName } from '../util'; +import { getStaticValue } from 'eslint-utils'; + +export default createRule({ + name: 'prefer-regexp-exec', + defaultOptions: [], + + meta: { + type: 'suggestion', + docs: { + description: + 'Prefer RegExp#exec() over String#match() if no global flag is provided.', + category: 'Best Practices', + recommended: false, + }, + messages: { + regExpExecOverStringMatch: 'Use the `RegExp#exec()` method instead.', + }, + schema: [], + }, + + create(context) { + const globalScope = context.getScope(); + const service = getParserServices(context); + const typeChecker = service.program.getTypeChecker(); + + /** + * Check if a given node is a string. + * @param node The node to check. + */ + function isStringType(node: TSESTree.Node): boolean { + const objectType = typeChecker.getTypeAtLocation( + service.esTreeNodeToTSNodeMap.get(node), + ); + return getTypeName(typeChecker, objectType) === 'string'; + } + + return { + "CallExpression[arguments.length=1] > MemberExpression.callee[property.name='match'][computed=false]"( + node: TSESTree.MemberExpression, + ) { + const callNode = node.parent as TSESTree.CallExpression; + const arg = callNode.arguments[0]; + const evaluated = getStaticValue(arg, globalScope); + + // Don't report regular expressions with global flag. + if ( + evaluated && + evaluated.value instanceof RegExp && + evaluated.value.flags.includes('g') + ) { + return; + } + + if (isStringType(node.object)) { + context.report({ + node: callNode, + messageId: 'regExpExecOverStringMatch', + }); + return; + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index 565a4cb83db..ce45e860068 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -5,8 +5,7 @@ import { getStaticValue, } from 'eslint-utils'; import { RegExpParser, AST as RegExpAST } from 'regexpp'; -import ts from 'typescript'; -import { createRule, getParserServices } from '../util'; +import { createRule, getParserServices, getTypeName } from '../util'; const EQ_OPERATORS = /^[=!]=/; const regexpp = new RegExpParser(); @@ -35,65 +34,17 @@ export default createRule({ const globalScope = context.getScope(); const sourceCode = context.getSourceCode(); const service = getParserServices(context); - const types = service.program.getTypeChecker(); - - /** - * Get the type name of a given type. - * @param type The type to get. - */ - function getTypeName(type: ts.Type): string { - // It handles `string` and string literal types as string. - if ((type.flags & ts.TypeFlags.StringLike) !== 0) { - return 'string'; - } - - // If the type is a type parameter which extends primitive string types, - // but it was not recognized as a string like. So check the constraint - // type of the type parameter. - if ((type.flags & ts.TypeFlags.TypeParameter) !== 0) { - // `type.getConstraint()` method doesn't return the constraint type of - // the type parameter for some reason. So this gets the constraint type - // via AST. - const node = type.symbol.declarations[0] as ts.TypeParameterDeclaration; - if (node.constraint != null) { - return getTypeName(types.getTypeFromTypeNode(node.constraint)); - } - } - - // If the type is a union and all types in the union are string like, - // return `string`. For example: - // - `"a" | "b"` is string. - // - `string | string[]` is not string. - if ( - type.isUnion() && - type.types.map(getTypeName).every(t => t === 'string') - ) { - return 'string'; - } - - // If the type is an intersection and a type in the intersection is string - // like, return `string`. For example: `string & {__htmlEscaped: void}` - if ( - type.isIntersection() && - type.types.map(getTypeName).some(t => t === 'string') - ) { - return 'string'; - } - - return types.typeToString(type); - } + const typeChecker = service.program.getTypeChecker(); /** * Check if a given node is a string. * @param node The node to check. */ function isStringType(node: TSESTree.Node): boolean { - const objectType = types.getTypeAtLocation( + const objectType = typeChecker.getTypeAtLocation( service.esTreeNodeToTSNodeMap.get(node), ); - const typeName = getTypeName(objectType); - - return typeName === 'string'; + return getTypeName(typeChecker, objectType) === 'string'; } /** diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index 866ff80a45e..55567f00aea 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -41,6 +41,63 @@ export function containsTypeByName( ); } +/** + * Get the type name of a given type. + * @param typeChecker The context sensitive TypeScript TypeChecker. + * @param type The type to get the name of. + */ +export function getTypeName( + typeChecker: ts.TypeChecker, + type: ts.Type, +): string { + // It handles `string` and string literal types as string. + if ((type.flags & ts.TypeFlags.StringLike) !== 0) { + return 'string'; + } + + // If the type is a type parameter which extends primitive string types, + // but it was not recognized as a string like. So check the constraint + // type of the type parameter. + if ((type.flags & ts.TypeFlags.TypeParameter) !== 0) { + // `type.getConstraint()` method doesn't return the constraint type of + // the type parameter for some reason. So this gets the constraint type + // via AST. + const node = type.symbol.declarations[0] as ts.TypeParameterDeclaration; + if (node.constraint != null) { + return getTypeName( + typeChecker, + typeChecker.getTypeFromTypeNode(node.constraint), + ); + } + } + + // If the type is a union and all types in the union are string like, + // return `string`. For example: + // - `"a" | "b"` is string. + // - `string | string[]` is not string. + if ( + type.isUnion() && + type.types + .map(value => getTypeName(typeChecker, value)) + .every(t => t === 'string') + ) { + return 'string'; + } + + // If the type is an intersection and a type in the intersection is string + // like, return `string`. For example: `string & {__htmlEscaped: void}` + if ( + type.isIntersection() && + type.types + .map(value => getTypeName(typeChecker, value)) + .some(t => t === 'string') + ) { + return 'string'; + } + + return typeChecker.typeToString(type); +} + /** * Resolves the given node's type. Will resolve to the type's generic constraint, if it has one. */ diff --git a/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts b/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts new file mode 100644 index 00000000000..98381cda6a1 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/prefer-regexp-exec.test.ts @@ -0,0 +1,180 @@ +import path from 'path'; +import rule from '../../src/rules/prefer-regexp-exec'; +import { RuleTester } from '../RuleTester'; + +const rootPath = path.join(process.cwd(), 'tests/fixtures/'); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig.json', + }, +}); + +ruleTester.run('prefer-regexp-exec', rule, { + valid: [ + '"something".match();', + '"something".match(/thing/g);', + ` +const text = "something"; +const search = /thing/g; +text.match(search); +`, + ` +const match = (s: RegExp) => "something"; +match(/thing/); +`, + ` +const a = {match : (s: RegExp) => "something"}; +a.match(/thing/); +`, + ` +function f(s: string | string[]) { + s.match(/e/); +} +`, + ], + invalid: [ + { + code: '"something".match(/thing/);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: ` +const text = "something"; +const search = /thing/; +text.match(search); +`, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 4, + column: 1, + }, + ], + }, + { + code: '"212".match(2);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: '"212".match(+2);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: '"oNaNo".match(NaN);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: + '"Infinity contains -Infinity and +Infinity in JavaScript.".match(Infinity);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: + '"Infinity contains -Infinity and +Infinity in JavaScript.".match(+Infinity);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: + '"Infinity contains -Infinity and +Infinity in JavaScript.".match(-Infinity);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: '"void and null".match(null);', + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 1, + column: 1, + }, + ], + }, + { + code: ` +function f(s: 'a' | 'b') { + s.match('a'); +} +`, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 3, + column: 3, + }, + ], + }, + { + code: ` +type SafeString = string & {__HTML_ESCAPED__: void} +function f(s: SafeString) { + s.match(/thing/); +} +`, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 4, + column: 3, + }, + ], + }, + { + code: ` +function f(s: T) { + s.match(/thing/); +} + `, + errors: [ + { + messageId: 'regExpExecOverStringMatch', + line: 3, + column: 3, + }, + ], + }, + ], +}); From 5c65350677ceae386b0697d9df86036c1e21bff9 Mon Sep 17 00:00:00 2001 From: Thomas den Hollander Date: Sat, 11 May 2019 21:28:56 +0200 Subject: [PATCH 12/41] fix(eslint-plugin): Fix exported name of eslint-recommended (#513) --- packages/eslint-plugin/src/index.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index 0b63396576a..e62e97495d5 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -19,6 +19,6 @@ export = { rules: rulesWithoutDefault, configs: { recommended, - eslintRecommended, + 'eslint-recommended': eslintRecommended, }, }; From 89c87cc607172c5cd3f49a563ac283441632fbff Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Sun, 12 May 2019 11:30:47 -0700 Subject: [PATCH 13/41] fix(eslint-plugin): Add missing dependency Fixes #516 --- packages/eslint-plugin/package.json | 1 + 1 file changed, 1 insertion(+) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 53d21ecf3da..363f96e7dba 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -39,6 +39,7 @@ "@typescript-eslint/experimental-utils": "1.8.0", "@typescript-eslint/parser": "1.8.0", "eslint-utils": "^1.3.1", + "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", "requireindex": "^1.2.0", "tsutils": "^3.7.0" From 2378fcaed4032d46ccedc10a623c1c1be6ea2182 Mon Sep 17 00:00:00 2001 From: James Henry Date: Sun, 12 May 2019 17:38:11 -0400 Subject: [PATCH 14/41] chore: publish v1.9.0 --- CHANGELOG.md | 11 +++++++++++ lerna.json | 2 +- packages/eslint-plugin-tslint/CHANGELOG.md | 4 ++++ packages/eslint-plugin-tslint/package.json | 4 ++-- packages/eslint-plugin/CHANGELOG.md | 11 +++++++++++ packages/eslint-plugin/package.json | 6 +++--- packages/experimental-utils/CHANGELOG.md | 4 ++++ packages/experimental-utils/package.json | 4 ++-- packages/parser/CHANGELOG.md | 4 ++++ packages/parser/package.json | 8 ++++---- packages/shared-fixtures/CHANGELOG.md | 8 ++++++++ packages/shared-fixtures/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 4 ++++ packages/typescript-estree/package.json | 4 ++-- 14 files changed, 61 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index cc51d8504d0..d372b10b817 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +### Bug Fixes + +- **eslint-plugin:** Add missing dependency ([89c87cc](https://github.com/typescript-eslint/typescript-eslint/commit/89c87cc)), closes [#516](https://github.com/typescript-eslint/typescript-eslint/issues/516) +- **eslint-plugin:** Fix exported name of eslint-recommended ([#513](https://github.com/typescript-eslint/typescript-eslint/issues/513)) ([5c65350](https://github.com/typescript-eslint/typescript-eslint/commit/5c65350)) + +### Features + +- **eslint-plugin:** add prefer-regexp-exec rule ([#305](https://github.com/typescript-eslint/typescript-eslint/issues/305)) ([f61d421](https://github.com/typescript-eslint/typescript-eslint/commit/f61d421)) + # [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) ### Bug Fixes diff --git a/lerna.json b/lerna.json index 3b33436bfbf..e8488a21d01 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.8.0", + "version": "1.9.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index d78fa383f77..3d82c4af6aa 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + # [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) ### Bug Fixes diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index f57aabccddf..b6bfa14be9e 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin-tslint", - "version": "1.8.0", + "version": "1.9.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -36,6 +36,6 @@ "devDependencies": { "@types/eslint": "^4.16.3", "@types/lodash.memoize": "^4.1.4", - "@typescript-eslint/parser": "1.8.0" + "@typescript-eslint/parser": "1.9.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index f33b4709976..a0d7f5fcdf0 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,17 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +### Bug Fixes + +- **eslint-plugin:** Add missing dependency ([89c87cc](https://github.com/typescript-eslint/typescript-eslint/commit/89c87cc)), closes [#516](https://github.com/typescript-eslint/typescript-eslint/issues/516) +- **eslint-plugin:** Fix exported name of eslint-recommended ([#513](https://github.com/typescript-eslint/typescript-eslint/issues/513)) ([5c65350](https://github.com/typescript-eslint/typescript-eslint/commit/5c65350)) + +### Features + +- **eslint-plugin:** add prefer-regexp-exec rule ([#305](https://github.com/typescript-eslint/typescript-eslint/issues/305)) ([f61d421](https://github.com/typescript-eslint/typescript-eslint/commit/f61d421)) + # [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) ### Bug Fixes diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 363f96e7dba..383c08dd392 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "1.8.0", + "version": "1.9.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -36,8 +36,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "1.8.0", - "@typescript-eslint/parser": "1.8.0", + "@typescript-eslint/experimental-utils": "1.9.0", + "@typescript-eslint/parser": "1.9.0", "eslint-utils": "^1.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", diff --git a/packages/experimental-utils/CHANGELOG.md b/packages/experimental-utils/CHANGELOG.md index dfb21f270f9..b9ee47917f1 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + # [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) ### Features diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 03f541c8b1b..5bafdb42c7d 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "1.8.0", + "version": "1.9.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -31,7 +31,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.8.0" + "@typescript-eslint/typescript-estree": "1.9.0" }, "peerDependencies": { "typescript": "*" diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 9d5e114267f..21ee31a3bd1 100644 --- a/packages/parser/CHANGELOG.md +++ b/packages/parser/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/parser + # [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) ### Bug Fixes diff --git a/packages/parser/package.json b/packages/parser/package.json index 2b64f359e2c..0389785b401 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "1.8.0", + "version": "1.9.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "files": [ @@ -37,13 +37,13 @@ "eslint": "^5.0.0" }, "dependencies": { - "@typescript-eslint/experimental-utils": "1.8.0", - "@typescript-eslint/typescript-estree": "1.8.0", + "@typescript-eslint/experimental-utils": "1.9.0", + "@typescript-eslint/typescript-estree": "1.9.0", "eslint-scope": "^4.0.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/shared-fixtures": "1.8.0" + "@typescript-eslint/shared-fixtures": "1.9.0" } } diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 18c73567c6e..53e0db712c2 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/CHANGELOG.md @@ -3,6 +3,14 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/shared-fixtures + + + + + # [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) **Note:** Version bump only for package @typescript-eslint/shared-fixtures diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 1d66074de06..88dba89c2d4 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "1.8.0", + "version": "1.9.0", "private": true } diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 88e97d5f779..4e717803c6a 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,10 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.9.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.8.0...v1.9.0) (2019-05-12) + +**Note:** Version bump only for package @typescript-eslint/typescript-estree + # [1.8.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.7.0...v1.8.0) (2019-05-10) ### Bug Fixes diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 3a3312a0708..65b6b41440f 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "1.8.0", + "version": "1.9.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -42,6 +42,6 @@ }, "devDependencies": { "@babel/types": "^7.3.2", - "@typescript-eslint/shared-fixtures": "1.8.0" + "@typescript-eslint/shared-fixtures": "1.9.0" } } From d9e5f15eb23d72633da7f92d4452314f65ef7994 Mon Sep 17 00:00:00 2001 From: Simen Bekkhus Date: Mon, 13 May 2019 01:59:07 +0200 Subject: [PATCH 15/41] fix(experimental-utils): add `endLine` and `endColumn` (#517) --- packages/experimental-utils/src/ts-eslint/RuleTester.ts | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts index fe9b4b80396..ea677806cf3 100644 --- a/packages/experimental-utils/src/ts-eslint/RuleTester.ts +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -32,6 +32,8 @@ interface TestCaseError { type?: AST_NODE_TYPES | AST_TOKEN_TYPES; line?: number; column?: number; + endLine?: number; + endColumn?: number; } interface RunTests< From 5ec2b32235ef343b574077075641eb4c68d1ae59 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 13 May 2019 09:57:59 -0700 Subject: [PATCH 16/41] fix(eslint-plugin): [no-extra-parens] Fix crash default switch case crash Fixes #509 --- packages/eslint-plugin/src/rules/no-extra-parens.ts | 2 +- packages/eslint-plugin/tests/rules/no-extra-parens.test.ts | 2 +- packages/typescript-estree/src/ts-estree/ts-estree.ts | 2 +- 3 files changed, 3 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index 7dd71ad583b..1b12e38e6ec 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -200,7 +200,7 @@ export default util.createRule({ } }, SwitchCase(node) { - if (node.test.type !== AST_NODE_TYPES.TSAsExpression) { + if (node.test && node.test.type !== AST_NODE_TYPES.TSAsExpression) { return rules.SwitchCase(node); } }, diff --git a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts index 64e93de21c3..6d84149efb8 100644 --- a/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts +++ b/packages/eslint-plugin/tests/rules/no-extra-parens.test.ts @@ -146,7 +146,7 @@ throw 1; const x = !(1 as 1); const x = (1 as 1)++; function *x() { yield (1 as 1); yield 1; } -switch (foo) { case 1: case (2 as 2): } +switch (foo) { case 1: case (2 as 2): break; default: break; } `, options: [ 'all', diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index ec7640b56b2..6bb54c5a775 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -928,7 +928,7 @@ export interface Super extends BaseNode { export interface SwitchCase extends BaseNode { type: AST_NODE_TYPES.SwitchCase; - test: Expression; + test: Expression | null; consequent: Statement[]; } From 1a0e60ba8d8bdcd32c57676bc12a49323804c883 Mon Sep 17 00:00:00 2001 From: Thomas den Hollander Date: Mon, 13 May 2019 19:07:10 +0200 Subject: [PATCH 17/41] fix(eslint-plugin): Remove `no-dupe-class-members` from eslint-recommended (#520) It was accidentally enabled. --- packages/eslint-plugin/src/configs/eslint-recommended.ts | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/configs/eslint-recommended.ts b/packages/eslint-plugin/src/configs/eslint-recommended.ts index 3e47d860173..a0f66c7a201 100644 --- a/packages/eslint-plugin/src/configs/eslint-recommended.ts +++ b/packages/eslint-plugin/src/configs/eslint-recommended.ts @@ -32,6 +32,8 @@ export default { 'no-this-before-super': 'off', // This is checked by Typescript using the option `strictNullChecks`. 'no-undef': 'off', + // This is already checked by Typescript. + 'no-dupe-class-members': 'off', /** * 2. Enable more ideomatic code */ @@ -41,8 +43,6 @@ export default { // The spread operator/rest parameters should be prefered in Typescript. 'prefer-rest-params': 'error', 'prefer-spread': 'error', - // This is already checked by Typescript. - 'no-dupe-class-members': 'error', }, }, ], From 8c8497ce2f1cf0dd5c9df91fe29415181c0d8096 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 13 May 2019 14:05:20 -0700 Subject: [PATCH 18/41] fix(eslint-plugin): [explicit-function-return-type] Fix obj setter prop Fixes #525 --- .../src/rules/explicit-function-return-type.ts | 3 ++- .../tests/rules/explicit-function-return-type.test.ts | 11 +++++++++++ packages/typescript-estree/src/ts-estree/ts-estree.ts | 2 +- 3 files changed, 14 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index 914e4172c79..be6bb14c448 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -65,7 +65,8 @@ export default util.createRule({ function isSetter(node: TSESTree.Node | undefined): boolean { return ( !!node && - node.type === AST_NODE_TYPES.MethodDefinition && + (node.type === AST_NODE_TYPES.MethodDefinition || + node.type === AST_NODE_TYPES.Property) && node.kind === 'set' ); } diff --git a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index efb98d1dd34..936b300134b 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -169,6 +169,17 @@ class App { `, options: [{ allowTypedFunctionExpressions: true }], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/525 + { + filename: 'test.ts', + code: ` +const myObj = { + set myProp(val) { + this.myProp = val; + }, +}; + `, + }, ], invalid: [ { diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index 6bb54c5a775..504280e4850 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -895,7 +895,7 @@ export interface Property extends BaseNode { computed: boolean; method: boolean; shorthand: boolean; - kind: 'init'; + kind: 'init' | 'get' | 'set'; } export interface RestElement extends BaseNode { From 67537b863a8a98383796abb404224fdfc1c7b996 Mon Sep 17 00:00:00 2001 From: Ricky Lippmann <3674067+ldrick@users.noreply.github.com> Date: Thu, 16 May 2019 00:32:10 +0200 Subject: [PATCH 19/41] feat(eslint-plugin): add config all.json (#313) --- .prettierignore | 1 - packages/eslint-plugin/README.md | 2 +- packages/eslint-plugin/package.json | 4 +- packages/eslint-plugin/src/configs/all.json | 64 ++++++++++ packages/eslint-plugin/src/configs/base.json | 7 ++ .../src/configs/recommended.json | 6 +- packages/eslint-plugin/src/index.ts | 20 +--- .../eslint-plugin/src/rules/ban-ts-ignore.ts | 2 +- packages/eslint-plugin/src/rules/index.ts | 111 ++++++++++++++++++ .../src/rules/no-require-imports.ts | 2 +- .../src/rules/promise-function-async.ts | 2 +- .../eslint-plugin/src/rules/unbound-method.ts | 2 +- .../eslint-plugin/tests/configs/all.test.ts | 46 ++++++++ packages/eslint-plugin/tests/index.test.ts | 24 ++++ .../eslint-plugin/tests/rules/index.test.ts | 14 +++ .../eslint-plugin/tools/generate-configs.ts | 109 +++++++++++++++++ .../eslint-plugin/tools/update-recommended.ts | 68 ----------- .../eslint-plugin/typings/requireindex.d.ts | 9 -- yarn.lock | 5 - 19 files changed, 389 insertions(+), 109 deletions(-) create mode 100644 packages/eslint-plugin/src/configs/all.json create mode 100644 packages/eslint-plugin/src/configs/base.json create mode 100644 packages/eslint-plugin/src/rules/index.ts create mode 100644 packages/eslint-plugin/tests/configs/all.test.ts create mode 100644 packages/eslint-plugin/tests/index.test.ts create mode 100644 packages/eslint-plugin/tests/rules/index.test.ts create mode 100644 packages/eslint-plugin/tools/generate-configs.ts delete mode 100644 packages/eslint-plugin/tools/update-recommended.ts delete mode 100644 packages/eslint-plugin/typings/requireindex.d.ts diff --git a/.prettierignore b/.prettierignore index 50a789d61de..755a2771d25 100644 --- a/.prettierignore +++ b/.prettierignore @@ -3,7 +3,6 @@ **/coverage **/shared-fixtures **/tests/integration/fixtures/**/* -**/lib/configs/recommended.json **/.vscode **/.nyc_output packages/eslint-plugin-tslint/tests/test-tslint-rules-directory/alwaysFailRule.js diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 22afc3659ac..24ada799b9a 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -161,7 +161,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | | :thought_balloon: | | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | | [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | :heavy_check_mark: | | :thought_balloon: | +| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | | | :thought_balloon: | | [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Enforce to use `RegExp#exec` over `String#match` | | | :thought_balloon: | | [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one. (`unified-signatures` from TSLint) | | | | diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 383c08dd392..3409c5984c6 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -29,10 +29,11 @@ "clean": "rimraf dist/", "docs": "eslint-docs", "docs:check": "eslint-docs check", + "test": "jest --coverage", + "generate:configs": "ts-node --files tools/generate-configs.ts", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "prebuild": "npm run clean", "recommended:update": "ts-node tools/update-recommended.ts", - "test": "jest --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -41,7 +42,6 @@ "eslint-utils": "^1.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", - "requireindex": "^1.2.0", "tsutils": "^3.7.0" }, "devDependencies": { diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json new file mode 100644 index 00000000000..fa36e3d876c --- /dev/null +++ b/packages/eslint-plugin/src/configs/all.json @@ -0,0 +1,64 @@ +{ + "extends": "./configs/base.json", + "rules": { + "@typescript-eslint/adjacent-overload-signatures": "error", + "@typescript-eslint/array-type": "error", + "@typescript-eslint/await-thenable": "error", + "@typescript-eslint/ban-ts-ignore": "error", + "@typescript-eslint/ban-types": "error", + "camelcase": "off", + "@typescript-eslint/camelcase": "error", + "@typescript-eslint/class-name-casing": "error", + "@typescript-eslint/explicit-function-return-type": "error", + "@typescript-eslint/explicit-member-accessibility": "error", + "@typescript-eslint/func-call-spacing": "error", + "@typescript-eslint/generic-type-naming": "error", + "indent": "off", + "@typescript-eslint/indent": "error", + "@typescript-eslint/interface-name-prefix": "error", + "@typescript-eslint/member-delimiter-style": "error", + "@typescript-eslint/member-naming": "error", + "@typescript-eslint/member-ordering": "error", + "@typescript-eslint/no-angle-bracket-type-assertion": "error", + "no-array-constructor": "off", + "@typescript-eslint/no-array-constructor": "error", + "@typescript-eslint/no-empty-interface": "error", + "@typescript-eslint/no-explicit-any": "error", + "@typescript-eslint/no-extra-parens": "error", + "@typescript-eslint/no-extraneous-class": "error", + "@typescript-eslint/no-for-in-array": "error", + "@typescript-eslint/no-inferrable-types": "error", + "@typescript-eslint/no-magic-numbers": "error", + "@typescript-eslint/no-misused-new": "error", + "@typescript-eslint/no-namespace": "error", + "@typescript-eslint/no-non-null-assertion": "error", + "@typescript-eslint/no-object-literal-type-assertion": "error", + "@typescript-eslint/no-parameter-properties": "error", + "@typescript-eslint/no-require-imports": "error", + "@typescript-eslint/no-this-alias": "error", + "@typescript-eslint/no-triple-slash-reference": "error", + "@typescript-eslint/no-type-alias": "error", + "@typescript-eslint/no-unnecessary-qualifier": "error", + "@typescript-eslint/no-unnecessary-type-assertion": "error", + "no-unused-vars": "off", + "@typescript-eslint/no-unused-vars": "error", + "@typescript-eslint/no-use-before-define": "error", + "no-useless-constructor": "off", + "@typescript-eslint/no-useless-constructor": "error", + "@typescript-eslint/no-var-requires": "error", + "@typescript-eslint/prefer-for-of": "error", + "@typescript-eslint/prefer-function-type": "error", + "@typescript-eslint/prefer-includes": "error", + "@typescript-eslint/prefer-interface": "error", + "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/prefer-regexp-exec": "error", + "@typescript-eslint/prefer-string-starts-ends-with": "error", + "@typescript-eslint/promise-function-async": "error", + "@typescript-eslint/require-array-sort-compare": "error", + "@typescript-eslint/restrict-plus-operands": "error", + "@typescript-eslint/semi": "error", + "@typescript-eslint/type-annotation-spacing": "error", + "@typescript-eslint/unbound-method": "error", + "@typescript-eslint/unified-signatures": "error" + } +} diff --git a/packages/eslint-plugin/src/configs/base.json b/packages/eslint-plugin/src/configs/base.json new file mode 100644 index 00000000000..9b6931ad616 --- /dev/null +++ b/packages/eslint-plugin/src/configs/base.json @@ -0,0 +1,7 @@ +{ + "parser": "@typescript-eslint/parser", + "parserOptions": { + "sourceType": "module" + }, + "plugins": ["@typescript-eslint"] +} diff --git a/packages/eslint-plugin/src/configs/recommended.json b/packages/eslint-plugin/src/configs/recommended.json index e107a645724..12edb521fe8 100644 --- a/packages/eslint-plugin/src/configs/recommended.json +++ b/packages/eslint-plugin/src/configs/recommended.json @@ -1,9 +1,5 @@ { - "parser": "@typescript-eslint/parser", - "parserOptions": { - "sourceType": "module" - }, - "plugins": ["@typescript-eslint"], + "extends": "./configs/base.json", "rules": { "@typescript-eslint/adjacent-overload-signatures": "error", "@typescript-eslint/array-type": "error", diff --git a/packages/eslint-plugin/src/index.ts b/packages/eslint-plugin/src/index.ts index e62e97495d5..0fb5516ba72 100644 --- a/packages/eslint-plugin/src/index.ts +++ b/packages/eslint-plugin/src/index.ts @@ -1,23 +1,15 @@ -import requireIndex from 'requireindex'; -import path from 'path'; +import rules from './rules'; +import all from './configs/all.json'; +import base from './configs/base.json'; import recommended from './configs/recommended.json'; import eslintRecommended from './configs/eslint-recommended'; -const rules = requireIndex(path.join(__dirname, 'rules')); -// eslint expects the rule to be on rules[name], not rules[name].default -const rulesWithoutDefault = Object.keys(rules).reduce>( - (acc, ruleName) => { - acc[ruleName] = rules[ruleName].default; - return acc; - }, - {}, -); - -// import all rules in lib/rules export = { - rules: rulesWithoutDefault, + rules, configs: { + all, + base, recommended, 'eslint-recommended': eslintRecommended, }, diff --git a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts index bf83a29cf4e..661031a03f7 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts @@ -8,7 +8,7 @@ export default util.createRule({ description: 'Bans “// @ts-ignore” comments from being used.', tslintRuleName: 'ban-ts-ignore', category: 'Best Practices', - recommended: 'error', + recommended: false, }, schema: [], messages: { diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts new file mode 100644 index 00000000000..d62f58d6af3 --- /dev/null +++ b/packages/eslint-plugin/src/rules/index.ts @@ -0,0 +1,111 @@ +import adjacentOverloadSignatures from './adjacent-overload-signatures'; +import arrayType from './array-type'; +import awaitThenable from './await-thenable'; +import banTsIgnore from './ban-ts-ignore'; +import banTypes from './ban-types'; +import camelcase from './camelcase'; +import classNameCasing from './class-name-casing'; +import explicitFunctionReturnType from './explicit-function-return-type'; +import explicitMemberAccessibility from './explicit-member-accessibility'; +import funcCallSpacing from './func-call-spacing'; +import genericTypeNaming from './generic-type-naming'; +import indent from './indent'; +import interfaceNamePrefix from './interface-name-prefix'; +import memberDelimiterStyle from './member-delimiter-style'; +import memberNaming from './member-naming'; +import memberOrdering from './member-ordering'; +import noAngleBracketTypeAssertion from './no-angle-bracket-type-assertion'; +import noArrayConstructor from './no-array-constructor'; +import noEmptyInterface from './no-empty-interface'; +import noExplicitAny from './no-explicit-any'; +import noExtraParens from './no-extra-parens'; +import noExtraneousClass from './no-extraneous-class'; +import noForInArray from './no-for-in-array'; +import noInferrableTypes from './no-inferrable-types'; +import noMagicNumbers from './no-magic-numbers'; +import noMisusedNew from './no-misused-new'; +import noNamespace from './no-namespace'; +import noNonNullAssertion from './no-non-null-assertion'; +import noObjectLiteralTypeAssertion from './no-object-literal-type-assertion'; +import noParameterProperties from './no-parameter-properties'; +import noRequireImports from './no-require-imports'; +import noThisAlias from './no-this-alias'; +import noTripleSlashReference from './no-triple-slash-reference'; +import noTypeAlias from './no-type-alias'; +import noUnnecessaryQualifier from './no-unnecessary-qualifier'; +import noUnnecessaryTypeAssertion from './no-unnecessary-type-assertion'; +import noUnusedVars from './no-unused-vars'; +import noUseBeforeDefine from './no-use-before-define'; +import noUselessConstructor from './no-useless-constructor'; +import noVarRequires from './no-var-requires'; +import preferForOf from './prefer-for-of'; +import preferFunctionType from './prefer-function-type'; +import preferIncludes from './prefer-includes'; +import preferInterface from './prefer-interface'; +import preferNamespaceKeyword from './prefer-namespace-keyword'; +import preferRegexpExec from './prefer-regexp-exec'; +import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; +import promiseFunctionAsync from './promise-function-async'; +import requireArraySortCompare from './require-array-sort-compare'; +import restrictPlusOperands from './restrict-plus-operands'; +import semi from './semi'; +import typeAnnotationSpacing from './type-annotation-spacing'; +import unboundMethod from './unbound-method'; +import unifiedSignatures from './unified-signatures'; + +export default { + 'adjacent-overload-signatures': adjacentOverloadSignatures, + 'array-type': arrayType, + 'await-thenable': awaitThenable, + 'ban-ts-ignore': banTsIgnore, + 'ban-types': banTypes, + camelcase: camelcase, + 'class-name-casing': classNameCasing, + 'explicit-function-return-type': explicitFunctionReturnType, + 'explicit-member-accessibility': explicitMemberAccessibility, + 'func-call-spacing': funcCallSpacing, + 'generic-type-naming': genericTypeNaming, + indent: indent, + 'interface-name-prefix': interfaceNamePrefix, + 'member-delimiter-style': memberDelimiterStyle, + 'member-naming': memberNaming, + 'member-ordering': memberOrdering, + 'no-angle-bracket-type-assertion': noAngleBracketTypeAssertion, + 'no-array-constructor': noArrayConstructor, + 'no-empty-interface': noEmptyInterface, + 'no-explicit-any': noExplicitAny, + 'no-extra-parens': noExtraParens, + 'no-extraneous-class': noExtraneousClass, + 'no-for-in-array': noForInArray, + 'no-inferrable-types': noInferrableTypes, + 'no-magic-numbers': noMagicNumbers, + 'no-misused-new': noMisusedNew, + 'no-namespace': noNamespace, + 'no-non-null-assertion': noNonNullAssertion, + 'no-object-literal-type-assertion': noObjectLiteralTypeAssertion, + 'no-parameter-properties': noParameterProperties, + 'no-require-imports': noRequireImports, + 'no-this-alias': noThisAlias, + 'no-triple-slash-reference': noTripleSlashReference, + 'no-type-alias': noTypeAlias, + 'no-unnecessary-qualifier': noUnnecessaryQualifier, + 'no-unnecessary-type-assertion': noUnnecessaryTypeAssertion, + 'no-unused-vars': noUnusedVars, + 'no-use-before-define': noUseBeforeDefine, + 'no-useless-constructor': noUselessConstructor, + 'no-var-requires': noVarRequires, + 'prefer-for-of': preferForOf, + 'prefer-function-type': preferFunctionType, + 'prefer-includes': preferIncludes, + 'prefer-interface': preferInterface, + 'prefer-namespace-keyword': preferNamespaceKeyword, + 'prefer-regexp-exec': preferRegexpExec, + 'prefer-string-starts-ends-with': preferStringStartsEndsWith, + 'promise-function-async': promiseFunctionAsync, + 'require-array-sort-compare': requireArraySortCompare, + 'restrict-plus-operands': restrictPlusOperands, + semi: semi, + 'type-annotation-spacing': typeAnnotationSpacing, + 'unbound-method': unboundMethod, + 'unified-signatures': unifiedSignatures, +}; diff --git a/packages/eslint-plugin/src/rules/no-require-imports.ts b/packages/eslint-plugin/src/rules/no-require-imports.ts index 69b4887c925..8bed451ccb7 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -9,7 +9,7 @@ export default util.createRule({ description: 'Disallows invocation of `require()`.', tslintName: 'no-require-imports', category: 'Best Practices', - recommended: 'error', + recommended: false, }, schema: [], messages: { diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index 0eb96e99143..ef041758630 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -21,7 +21,7 @@ export default util.createRule({ 'Requires any function or method that returns a Promise to be marked async.', tslintName: 'promise-function-async', category: 'Best Practices', - recommended: 'error', + recommended: false, }, messages: { missingAsync: 'Functions that return promises must be async.', diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index cd273a65456..cb9e388370e 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -26,7 +26,7 @@ export default util.createRule({ description: 'Enforces unbound methods are called with their expected scope.', tslintName: 'no-unbound-method', - recommended: 'error', + recommended: false, }, messages: { unbound: diff --git a/packages/eslint-plugin/tests/configs/all.test.ts b/packages/eslint-plugin/tests/configs/all.test.ts new file mode 100644 index 00000000000..425e3a1426a --- /dev/null +++ b/packages/eslint-plugin/tests/configs/all.test.ts @@ -0,0 +1,46 @@ +import rules from '../../src/rules'; +import allConfig from '../../src/configs/all.json'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; + +interface IndexRules { + [name: string]: TSESLint.RuleModule; +} +interface JsonRules { + [name: string]: string; +} + +describe('all.json config', () => { + const RULE_NAME_PREFIX = '@typescript-eslint/'; + + const typedRules: IndexRules = rules; + const notDeprecatedRuleNames = Object.keys(typedRules).reduce( + (collection, name) => { + if (!typedRules[name].meta.deprecated) { + collection.push(`${RULE_NAME_PREFIX}${name}`); + } + return collection; + }, + [], + ); + + // with end of Node.js 6 support, we can use Object.entries(allConfig.rules) here + const configRules: JsonRules = allConfig.rules; + const typescriptEslintConfigRules = Object.keys(configRules).filter(name => + name.startsWith(RULE_NAME_PREFIX), + ); + const typescriptEslintConfigRuleValues = typescriptEslintConfigRules.map( + name => configRules[name], + ); + + it('contains all @typescript-eslint/eslint-plugin rule modules, except the deprecated ones', () => { + expect(notDeprecatedRuleNames).toEqual( + expect.arrayContaining(typescriptEslintConfigRules), + ); + }); + + it('has all containing @typescript-eslint/eslint-plugin rules enabled with "error"', () => { + expect(['error']).toEqual( + expect.arrayContaining(typescriptEslintConfigRuleValues), + ); + }); +}); diff --git a/packages/eslint-plugin/tests/index.test.ts b/packages/eslint-plugin/tests/index.test.ts new file mode 100644 index 00000000000..3cac8304e9c --- /dev/null +++ b/packages/eslint-plugin/tests/index.test.ts @@ -0,0 +1,24 @@ +import fs from 'fs'; +import path from 'path'; + +import eslintPlugin from '../src'; +import rules from '../src/rules'; + +describe('eslint-plugin ("./src/index.ts")', () => { + const ruleKeys = Object.keys(rules); + const eslintPluginRuleKeys = Object.keys(eslintPlugin.rules); + + const configs = fs + .readdirSync('./src/configs') + .filter(file => ['.json', '.ts'].includes(path.extname(file).toLowerCase())) + .map(file => path.basename(file, path.extname(file))); + const eslintPluginConfigKeys = Object.keys(eslintPlugin.configs); + + it('exports all available rules', () => { + expect(ruleKeys).toEqual(expect.arrayContaining(eslintPluginRuleKeys)); + }); + + it('exports all available configs', () => { + expect(configs).toEqual(expect.arrayContaining(eslintPluginConfigKeys)); + }); +}); diff --git a/packages/eslint-plugin/tests/rules/index.test.ts b/packages/eslint-plugin/tests/rules/index.test.ts new file mode 100644 index 00000000000..c9160c0522f --- /dev/null +++ b/packages/eslint-plugin/tests/rules/index.test.ts @@ -0,0 +1,14 @@ +import fs from 'fs'; + +import rules from '../../src/rules'; + +describe('./src/rules/index.ts', () => { + const ruleNames = Object.keys(rules).map(name => `${name}.ts`); + const files = fs + .readdirSync('./src/rules') + .filter(file => file !== 'index.ts' && file.endsWith('.ts')); + + it('imports all available rule modules', () => { + expect(ruleNames).toEqual(expect.arrayContaining(files)); + }); +}); diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts new file mode 100644 index 00000000000..4c29753a048 --- /dev/null +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -0,0 +1,109 @@ +/* eslint-disable no-console */ + +import { Linter } from 'eslint'; +import fs from 'fs'; +import path from 'path'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import rules from '../src/rules'; + +interface LinterConfigRules { + [name: string]: Linter.RuleLevel | Linter.RuleLevelAndOptions; +} + +interface LinterConfig extends Linter.Config { + extends?: string | string[]; + plugins?: string[]; +} + +const RULE_NAME_PREFIX = '@typescript-eslint/'; +const MAX_RULE_NAME_LENGTH = 32 + RULE_NAME_PREFIX.length; +const DEFAULT_RULE_SETTING = 'warn'; +const BASE_RULES_TO_BE_OVERRIDDEN = new Set([ + 'camelcase', + 'indent', + 'no-array-constructor', + 'no-unused-vars', + 'no-useless-constructor', +]); + +const ruleEntries = Object.entries(rules); + +/** + * Helper function reduces records to key - value pairs. + * @param config + * @param entry + */ +const reducer = ( + config: LinterConfigRules, + entry: [string, TSESLint.RuleModule], + settings: { + errorLevel?: 'error' | 'warn'; + filterDeprecated: boolean; + }, +) => { + const key = entry[0]; + const value = entry[1]; + + if (settings.filterDeprecated && value.meta.deprecated) { + return config; + } + + const ruleName = `${RULE_NAME_PREFIX}${key}`; + const recommendation = value.meta.docs.recommended; + const usedSetting = settings.errorLevel + ? settings.errorLevel + : !recommendation + ? DEFAULT_RULE_SETTING + : recommendation; + + if (BASE_RULES_TO_BE_OVERRIDDEN.has(key)) { + console.log(key.padEnd(MAX_RULE_NAME_LENGTH), '=', 'off'); + config[key] = 'off'; + } + console.log(ruleName.padEnd(MAX_RULE_NAME_LENGTH), '=', usedSetting); + config[ruleName] = usedSetting; + + return config; +}; + +/** + * Helper function writes configuration. + */ +function writeConfig(config: LinterConfig, filePath: string): void { + fs.writeFileSync(filePath, `${JSON.stringify(config, null, 2)}\n`); +} + +const baseConfig: LinterConfig = { + parser: '@typescript-eslint/parser', + parserOptions: { + sourceType: 'module', + }, + plugins: ['@typescript-eslint'], +}; +writeConfig(baseConfig, path.resolve(__dirname, '../src/configs/base.json')); + +console.log('------------------------- all.json -------------------------'); +const allConfig: LinterConfig = { + extends: './configs/base.json', + rules: ruleEntries.reduce( + (config, entry) => + reducer(config, entry, { errorLevel: 'error', filterDeprecated: true }), + {}, + ), +}; +writeConfig(allConfig, path.resolve(__dirname, '../src/configs/all.json')); + +console.log('--------------------- recommended.json ---------------------'); +const recommendedConfig: LinterConfig = { + extends: './configs/base.json', + rules: ruleEntries + .filter(entry => !!entry[1].meta.docs.recommended) + .reduce( + (config, entry) => reducer(config, entry, { filterDeprecated: true }), + {}, + ), +}; +writeConfig( + recommendedConfig, + path.resolve(__dirname, '../src/configs/recommended.json'), +); diff --git a/packages/eslint-plugin/tools/update-recommended.ts b/packages/eslint-plugin/tools/update-recommended.ts deleted file mode 100644 index 39861d35ee7..00000000000 --- a/packages/eslint-plugin/tools/update-recommended.ts +++ /dev/null @@ -1,68 +0,0 @@ -/* eslint-disable no-console */ - -import path from 'path'; -import fs from 'fs'; -import requireIndex from 'requireindex'; - -const bannedRecommendedRules = new Set([ - 'camelcase', - 'indent', - 'no-array-constructor', - 'no-unused-vars', -]); -const MAX_RULE_NAME_LENGTH = 32 + 'typescript/'.length; - -function padEnd(str: string, length: number): string { - while (str.length < length) { - str += ' '; - } - return str; -} - -/** - * Generate recommended configuration - */ -function generate(): void { - // replace this with Object.entries when node > 8 - const allRules = requireIndex(path.resolve(__dirname, '../dist/lib/rules')); - - const rules = Object.keys(allRules) - .filter(key => !!allRules[key].meta.docs.recommended) - .reduce>((config, key) => { - // having this here is just for output niceness (the keys will be ordered) - if (bannedRecommendedRules.has(key)) { - console.log(padEnd(key, MAX_RULE_NAME_LENGTH), '= off'); - config[key] = 'off'; - } - - const ruleName = `@typescript-eslint/${key}`; - const setting = allRules[key].meta.docs.recommended; - - if (!['error', 'warn'].includes(setting)) { - console.log(`ERR! Invalid level for rule ${key}: "${setting}"`); - // Don't want to throw an error since ^ explains what happened. - // eslint-disable-next-line no-process-exit - process.exit(1); - } - - console.log(padEnd(ruleName, MAX_RULE_NAME_LENGTH), '=', setting); - config[ruleName] = setting; - - return config; - }, {}); - - const filePath = path.resolve(__dirname, '../src/configs/recommended.json'); - - const recommendedConfig = { - parser: '@typescript-eslint/parser', - parserOptions: { - sourceType: 'module', - }, - plugins: ['@typescript-eslint'], - rules, - }; - - fs.writeFileSync(filePath, `${JSON.stringify(recommendedConfig, null, 4)}\n`); -} - -generate(); diff --git a/packages/eslint-plugin/typings/requireindex.d.ts b/packages/eslint-plugin/typings/requireindex.d.ts deleted file mode 100644 index 03807931eed..00000000000 --- a/packages/eslint-plugin/typings/requireindex.d.ts +++ /dev/null @@ -1,9 +0,0 @@ -declare module 'requireindex' { - type RequireIndex = ( - path: string, - basenames?: string[], - ) => Record; - - const fn: RequireIndex; - export = fn; -} diff --git a/yarn.lock b/yarn.lock index b296e145ad2..6541649af58 100644 --- a/yarn.lock +++ b/yarn.lock @@ -6308,11 +6308,6 @@ require-main-filename@^2.0.0: resolved "https://registry.yarnpkg.com/require-main-filename/-/require-main-filename-2.0.0.tgz#d0b329ecc7cc0f61649f62215be69af54aa8989b" integrity sha512-NKN5kMDylKuldxYLSUfrbo5Tuzh4hd+2E8NPPX02mZtn1VuREQToYe/ZdlJy+J3uCpfaiGF05e7B8W0iXbQHmg== -requireindex@^1.2.0: - version "1.2.0" - resolved "https://registry.yarnpkg.com/requireindex/-/requireindex-1.2.0.tgz#3463cdb22ee151902635aa6c9535d4de9c2ef1ef" - integrity sha512-L9jEkOi3ASd9PYit2cwRfyppc9NoABujTP8/5gFcbERmo5jUoAKovIC3fsF17pkTnGsrByysqX+Kxd2OTNI1ww== - resolve-cwd@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/resolve-cwd/-/resolve-cwd-2.0.0.tgz#00a9f7387556e27038eae232caa372a6a59b665a" From 370ac729689905384adb20f92240264660fcc9bc Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 15 May 2019 17:35:52 -0700 Subject: [PATCH 20/41] feat: make utils/TSESLint export typed classes instead of just types (#526) --- .eslintrc.json | 7 +- packages/eslint-plugin-tslint/package.json | 3 +- packages/eslint-plugin-tslint/src/index.ts | 159 +--------------- .../eslint-plugin-tslint/src/rules/config.ts | 176 ++++++++++++++++++ .../eslint-plugin-tslint/tests/index.spec.ts | 84 +++++---- packages/eslint-plugin/package.json | 2 +- .../src/configs/eslint-recommended.ts | 2 + .../src/util/getParserServices.ts | 6 +- packages/eslint-plugin/tests/RuleTester.ts | 4 +- .../tests/rules/array-type.test.ts | 4 +- .../tests/rules/await-thenable.test.ts | 12 +- .../no-unnecessary-type-assertion.test.ts | 11 +- .../tests/rules/prefer-function-type.test.ts | 2 +- .../rules/promise-function-async.test.ts | 12 +- .../tests/rules/unified-signatures.test.ts | 2 +- .../eslint-plugin/tools/generate-configs.ts | 9 +- packages/experimental-utils/package.json | 1 + .../src/ts-eslint/CLIEngine.ts | 90 +++++++++ .../src/ts-eslint/Linter.ts | 26 ++- .../src/ts-eslint/ParserOptions.ts | 2 +- .../experimental-utils/src/ts-eslint/Rule.ts | 18 +- .../src/ts-eslint/RuleTester.ts | 9 +- .../src/ts-eslint/SourceCode.ts | 98 +++++----- .../experimental-utils/tsconfig.build.json | 2 +- .../experimental-utils/typings/eslint.d.ts | 15 ++ packages/parser/src/parser-options.ts | 24 +-- packages/parser/tests/lib/basics.ts | 12 +- packages/parser/tests/lib/parser.ts | 5 +- packages/parser/tests/lib/tsx.ts | 4 +- packages/typescript-estree/src/convert.ts | 4 +- .../typescript-estree/src/tsconfig-parser.ts | 2 +- yarn.lock | 15 +- 32 files changed, 479 insertions(+), 343 deletions(-) create mode 100644 packages/eslint-plugin-tslint/src/rules/config.ts create mode 100644 packages/experimental-utils/src/ts-eslint/CLIEngine.ts create mode 100644 packages/experimental-utils/typings/eslint.d.ts diff --git a/.eslintrc.json b/.eslintrc.json index 00e766eaaef..649b9ec2502 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -5,11 +5,14 @@ "es6": true, "node": true }, - "extends": ["eslint:recommended", "plugin:@typescript-eslint/recommended"], + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], "rules": { "comma-dangle": ["error", "always-multiline"], "curly": ["error", "all"], - "no-dupe-class-members": "off", "no-mixed-operators": "error", "no-console": "off", "no-dupe-class-members": "off", diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index b6bfa14be9e..fba222f6dbf 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -27,6 +27,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@typescript-eslint/experimental-utils": "1.9.0", "lodash.memoize": "^4.1.2" }, "peerDependencies": { @@ -34,7 +35,7 @@ "tslint": "^5.0.0" }, "devDependencies": { - "@types/eslint": "^4.16.3", + "@types/json-schema": "^7.0.3", "@types/lodash.memoize": "^4.1.4", "@typescript-eslint/parser": "1.9.0" } diff --git a/packages/eslint-plugin-tslint/src/index.ts b/packages/eslint-plugin-tslint/src/index.ts index 46574a106c8..a638ae2ba56 100644 --- a/packages/eslint-plugin-tslint/src/index.ts +++ b/packages/eslint-plugin-tslint/src/index.ts @@ -1,160 +1,9 @@ -import { Rule } from 'eslint'; -import memoize from 'lodash.memoize'; -import { Configuration, RuleSeverity } from 'tslint'; -import { Program } from 'typescript'; -import { CustomLinter } from './custom-linter'; -import { ParserServices } from '@typescript-eslint/typescript-estree'; - -//------------------------------------------------------------------------------ -// Plugin Definition -//------------------------------------------------------------------------------ - -type RawRuleConfig = - | null - | undefined - | boolean - | any[] - | { - severity?: RuleSeverity | 'warn' | 'none' | 'default'; - options?: any; - }; - -interface RawRulesConfig { - [key: string]: RawRuleConfig; -} +import configRule from './rules/config'; /** - * Construct a configFile for TSLint + * Expose a single rule called "config", which will be accessed in the user's eslint config files + * via "tslint/config" */ -const tslintConfig = memoize( - ( - lintFile: string, - tslintRules: RawRulesConfig, - tslintRulesDirectory: string[], - ) => { - if (lintFile != null) { - return Configuration.loadConfigurationFromPath(lintFile); - } - return Configuration.parseConfigFile({ - rules: tslintRules || {}, - rulesDirectory: tslintRulesDirectory || [], - }); - }, - (lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) => - `${lintFile}_${Object.keys(tslintRules).join(',')}_${ - tslintRulesDirectory.length - }`, -); - export const rules = { - /** - * Expose a single rule called "config", which will be accessed in the user's eslint config files - * via "tslint/config" - */ - config: { - meta: { - docs: { - description: - 'Wraps a TSLint configuration and lints the whole source using TSLint', - category: 'TSLint', - }, - schema: [ - { - type: 'object', - properties: { - rules: { - type: 'object', - /** - * No fixed schema properties for rules, as this would be a permanently moving target - */ - additionalProperties: true, - }, - rulesDirectory: { - type: 'array', - items: { - type: 'string', - }, - }, - lintFile: { - type: 'string', - }, - }, - additionalProperties: false, - }, - ], - }, - create(context: Rule.RuleContext) { - const fileName = context.getFilename(); - const sourceCode = context.getSourceCode().text; - const parserServices: ParserServices | undefined = context.parserServices; - - /** - * The user needs to have configured "project" in their parserOptions - * for @typescript-eslint/parser - */ - if (!parserServices || !parserServices.program) { - throw new Error( - `You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`, - ); - } - - /** - * The TSLint rules configuration passed in by the user - */ - const { - rules: tslintRules, - rulesDirectory: tslintRulesDirectory, - lintFile, - } = context.options[0]; - - const program: Program = parserServices.program; - - /** - * Create an instance of TSLint - * Lint the source code using the configured TSLint instance, and the rules which have been - * passed via the ESLint rule options for this rule (using "tslint/config") - */ - const tslintOptions = { - formatter: 'json', - fix: false, - }; - const tslint = new CustomLinter(tslintOptions, program); - const configuration = tslintConfig( - lintFile, - tslintRules, - tslintRulesDirectory, - ); - tslint.lint(fileName, sourceCode, configuration); - - const result = tslint.getResult(); - - /** - * Format the TSLint results for ESLint - */ - if (result.failures && result.failures.length) { - result.failures.forEach(failure => { - const start = failure.getStartPosition().getLineAndCharacter(); - const end = failure.getEndPosition().getLineAndCharacter(); - context.report({ - message: `${failure.getFailure()} (tslint:${failure.getRuleName()})`, - loc: { - start: { - line: start.line + 1, - column: start.character, - }, - end: { - line: end.line + 1, - column: end.character, - }, - }, - }); - }); - } - - /** - * Return an empty object for the ESLint rule - */ - return {}; - }, - }, + config: configRule, }; diff --git a/packages/eslint-plugin-tslint/src/rules/config.ts b/packages/eslint-plugin-tslint/src/rules/config.ts new file mode 100644 index 00000000000..e9cd3f53bb5 --- /dev/null +++ b/packages/eslint-plugin-tslint/src/rules/config.ts @@ -0,0 +1,176 @@ +import { + ESLintUtils, + ParserServices, +} from '@typescript-eslint/experimental-utils'; +import memoize from 'lodash.memoize'; +import { Configuration, RuleSeverity } from 'tslint'; +import { CustomLinter } from '../custom-linter'; + +// note - cannot migrate this to an import statement because it will make TSC copy the package.json to the dist folder +const version = require('../../package.json').version; + +const createRule = ESLintUtils.RuleCreator( + () => + `https://github.com/typescript-eslint/typescript-eslint/blob/v${version}/packages/eslint-plugin-tslint/README.md`, +); +export type RawRulesConfig = Record< + string, + | null + | undefined + | boolean + | any[] + | { + severity?: RuleSeverity | 'warn' | 'none' | 'default'; + options?: any; + } +>; + +export type MessageIds = 'failure'; +export type Options = [ + { + rules?: RawRulesConfig; + rulesDirectory?: string[]; + lintFile?: string; + } +]; + +/** + * Construct a configFile for TSLint + */ +const tslintConfig = memoize( + ( + lintFile?: string, + tslintRules?: RawRulesConfig, + tslintRulesDirectory?: string[], + ) => { + if (lintFile != null) { + return Configuration.loadConfigurationFromPath(lintFile); + } + return Configuration.parseConfigFile({ + rules: tslintRules || {}, + rulesDirectory: tslintRulesDirectory || [], + }); + }, + (lintFile: string | undefined, tslintRules = {}, tslintRulesDirectory = []) => + `${lintFile}_${Object.keys(tslintRules).join(',')}_${ + tslintRulesDirectory.length + }`, +); + +export default createRule({ + name: 'config', + meta: { + docs: { + description: + 'Wraps a TSLint configuration and lints the whole source using TSLint', + category: 'TSLint' as any, + recommended: false, + }, + type: 'problem', + messages: { + failure: '{{message}} (tslint:{{ruleName}})`', + }, + schema: [ + { + type: 'object', + properties: { + rules: { + type: 'object', + /** + * No fixed schema properties for rules, as this would be a permanently moving target + */ + additionalProperties: true, + }, + rulesDirectory: { + type: 'array', + items: { + type: 'string', + }, + }, + lintFile: { + type: 'string', + }, + }, + additionalProperties: false, + }, + ], + }, + defaultOptions: [] as any, + create(context) { + const fileName = context.getFilename(); + const sourceCode = context.getSourceCode().text; + const parserServices: ParserServices | undefined = context.parserServices; + + /** + * The user needs to have configured "project" in their parserOptions + * for @typescript-eslint/parser + */ + if (!parserServices || !parserServices.program) { + throw new Error( + `You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`, + ); + } + + /** + * The TSLint rules configuration passed in by the user + */ + const { + rules: tslintRules, + rulesDirectory: tslintRulesDirectory, + lintFile, + } = context.options[0]; + + const program = parserServices.program; + + /** + * Create an instance of TSLint + * Lint the source code using the configured TSLint instance, and the rules which have been + * passed via the ESLint rule options for this rule (using "tslint/config") + */ + const tslintOptions = { + formatter: 'json', + fix: false, + }; + const tslint = new CustomLinter(tslintOptions, program); + const configuration = tslintConfig( + lintFile, + tslintRules, + tslintRulesDirectory, + ); + tslint.lint(fileName, sourceCode, configuration); + + const result = tslint.getResult(); + + /** + * Format the TSLint results for ESLint + */ + if (result.failures && result.failures.length) { + result.failures.forEach(failure => { + const start = failure.getStartPosition().getLineAndCharacter(); + const end = failure.getEndPosition().getLineAndCharacter(); + context.report({ + messageId: 'failure', + data: { + message: failure.getFailure(), + ruleName: failure.getRuleName(), + }, + loc: { + start: { + line: start.line + 1, + column: start.character, + }, + end: { + line: end.line + 1, + column: end.character, + }, + }, + }); + }); + } + + /** + * Return an empty object for the ESLint rule + */ + return {}; + }, +}); diff --git a/packages/eslint-plugin-tslint/tests/index.spec.ts b/packages/eslint-plugin-tslint/tests/index.spec.ts index c62980fb398..516f5a741b4 100644 --- a/packages/eslint-plugin-tslint/tests/index.spec.ts +++ b/packages/eslint-plugin-tslint/tests/index.spec.ts @@ -1,8 +1,8 @@ -import { rules } from '../src'; -import { Linter, RuleTester } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import { readFileSync } from 'fs'; +import rule, { Options } from '../src/rules/config'; -const ruleTester = new RuleTester({ +const ruleTester = new TSESLint.RuleTester({ parserOptions: { ecmaVersion: 6, sourceType: 'module', @@ -19,29 +19,33 @@ const ruleTester = new RuleTester({ /** * Inline rules should be supported */ -const tslintRulesConfig = { - rules: { - semicolon: [true, 'always'], +const tslintRulesConfig: Options = [ + { + rules: { + semicolon: [true, 'always'], + }, }, -}; +]; /** * Custom rules directories should be supported */ -const tslintRulesDirectoryConfig = { - rulesDirectory: ['./tests/test-tslint-rules-directory'], - rules: { - 'always-fail': { - severity: 'error', +const tslintRulesDirectoryConfig: Options = [ + { + rulesDirectory: ['./tests/test-tslint-rules-directory'], + rules: { + 'always-fail': { + severity: 'error', + }, }, }, -}; +]; -ruleTester.run('tslint/config', rules.config, { +ruleTester.run('tslint/config', rule, { valid: [ { code: 'var foo = true;', - options: [tslintRulesConfig], + options: tslintRulesConfig, }, { filename: './tests/test-project/file-spec.ts', @@ -52,15 +56,11 @@ ruleTester.run('tslint/config', rules.config, { parserOptions: { project: `${__dirname}/test-project/tsconfig.json`, }, - options: [ - { - ...tslintRulesConfig, - }, - ], + options: tslintRulesConfig, }, { code: 'throw "should be ok because rule is not loaded";', - options: [tslintRulesConfig], + options: tslintRulesConfig, }, ], @@ -70,18 +70,26 @@ ruleTester.run('tslint/config', rules.config, { code: 'throw "err" // no-string-throw', errors: [ { - message: - 'Throwing plain strings (not instances of Error) gives no stack traces (tslint:no-string-throw)', + messageId: 'failure', + data: { + message: + 'Throwing plain strings (not instances of Error) gives no stack traces', + ruleName: 'no-string-throw', + }, }, ], }, { code: 'var foo = true // semicolon', - options: [tslintRulesConfig], + options: tslintRulesConfig, output: 'var foo = true // semicolon', errors: [ { - message: 'Missing semicolon (tslint:semicolon)', + messageId: 'failure', + data: { + message: 'Missing semicolon', + ruleName: 'semicolon', + }, line: 1, column: 15, }, @@ -89,11 +97,15 @@ ruleTester.run('tslint/config', rules.config, { }, { code: 'var foo = true // fail', - options: [tslintRulesDirectoryConfig], + options: tslintRulesDirectoryConfig, output: 'var foo = true // fail', errors: [ { - message: 'failure (tslint:always-fail)', + messageId: 'failure', + data: { + message: 'failure', + ruleName: 'always-fail', + }, line: 1, column: 1, }, @@ -118,8 +130,12 @@ ruleTester.run('tslint/config', rules.config, { ], errors: [ { - message: - 'Operands of \'+\' operation must either be both strings or both numbers, but found 1 + "2". Consider using template literals. (tslint:restrict-plus-operands)', + messageId: 'failure', + data: { + message: + 'Operands of \'+\' operation must either be both strings or both numbers, but found 1 + "2". Consider using template literals.', + ruleName: 'restrict-plus-operands', + }, }, ], }, @@ -127,9 +143,9 @@ ruleTester.run('tslint/config', rules.config, { }); describe('tslint/error', () => { - function testOutput(code: string, config: Linter.Config): void { - const linter = new Linter(); - linter.defineRule('tslint/config', rules.config); + function testOutput(code: string, config: TSESLint.Linter.Config): void { + const linter = new TSESLint.Linter(); + linter.defineRule('tslint/config', rule); expect(() => linter.verify(code, config)).toThrow( `You must provide a value for the "parserOptions.project" property for @typescript-eslint/parser`, @@ -157,9 +173,9 @@ describe('tslint/error', () => { }); it('should not crash if there is no tslint rules specified', () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); jest.spyOn(console, 'warn').mockImplementation(); - linter.defineRule('tslint/config', rules.config); + linter.defineRule('tslint/config', rule); expect(() => linter.verify('foo;', { parserOptions: { diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 3409c5984c6..a52aacc2352 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -38,7 +38,6 @@ }, "dependencies": { "@typescript-eslint/experimental-utils": "1.9.0", - "@typescript-eslint/parser": "1.9.0", "eslint-utils": "^1.3.1", "functional-red-black-tree": "^1.0.1", "regexpp": "^2.0.1", @@ -48,6 +47,7 @@ "eslint-docs": "^0.2.6" }, "peerDependencies": { + "@typescript-eslint/parser": "1.9.0", "eslint": "^5.0.0" } } diff --git a/packages/eslint-plugin/src/configs/eslint-recommended.ts b/packages/eslint-plugin/src/configs/eslint-recommended.ts index a0f66c7a201..283cd46aa2f 100644 --- a/packages/eslint-plugin/src/configs/eslint-recommended.ts +++ b/packages/eslint-plugin/src/configs/eslint-recommended.ts @@ -34,6 +34,8 @@ export default { 'no-undef': 'off', // This is already checked by Typescript. 'no-dupe-class-members': 'off', + // This is already checked by Typescript. + 'no-redeclare': 'off', /** * 2. Enable more ideomatic code */ diff --git a/packages/eslint-plugin/src/util/getParserServices.ts b/packages/eslint-plugin/src/util/getParserServices.ts index 2cc8b498159..84a9dea9874 100644 --- a/packages/eslint-plugin/src/util/getParserServices.ts +++ b/packages/eslint-plugin/src/util/getParserServices.ts @@ -1,5 +1,7 @@ -import { ParserServices } from '@typescript-eslint/parser'; -import { TSESLint } from '@typescript-eslint/experimental-utils'; +import { + ParserServices, + TSESLint, +} from '@typescript-eslint/experimental-utils'; type RequiredParserServices = { [k in keyof ParserServices]: Exclude diff --git a/packages/eslint-plugin/tests/RuleTester.ts b/packages/eslint-plugin/tests/RuleTester.ts index 4db8bf3909c..34e99972bf7 100644 --- a/packages/eslint-plugin/tests/RuleTester.ts +++ b/packages/eslint-plugin/tests/RuleTester.ts @@ -1,8 +1,8 @@ import { TSESLint, ESLintUtils } from '@typescript-eslint/experimental-utils'; -import { RuleTester as ESLintRuleTester } from 'eslint'; import * as path from 'path'; -const RuleTester: TSESLint.RuleTester = ESLintRuleTester as any; +// re-export the RuleTester from here to make it easier to do the tests +const RuleTester = TSESLint.RuleTester; function getFixturesRootDir() { return path.join(process.cwd(), 'tests/fixtures/'); diff --git a/packages/eslint-plugin/tests/rules/array-type.test.ts b/packages/eslint-plugin/tests/rules/array-type.test.ts index 31d0b018d32..70343f688fc 100644 --- a/packages/eslint-plugin/tests/rules/array-type.test.ts +++ b/packages/eslint-plugin/tests/rules/array-type.test.ts @@ -1,6 +1,6 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/array-type'; import { RuleTester } from '../RuleTester'; -import { Linter } from 'eslint'; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -889,7 +889,7 @@ describe('array-type (nested)', () => { describe('should deeply fix correctly', () => { function testOutput(option: string, code: string, output: string): void { it(code, () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); linter.defineRule('array-type', Object.assign({}, rule) as any); const result = linter.verifyAndFix( diff --git a/packages/eslint-plugin/tests/rules/await-thenable.test.ts b/packages/eslint-plugin/tests/rules/await-thenable.test.ts index deca521aefa..5ee0f7d0341 100644 --- a/packages/eslint-plugin/tests/rules/await-thenable.test.ts +++ b/packages/eslint-plugin/tests/rules/await-thenable.test.ts @@ -2,16 +2,14 @@ import rule from '../../src/rules/await-thenable'; import { RuleTester, getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); -const parserOptions = { - ecmaVersion: 2018, - tsconfigRootDir: rootDir, - project: './tsconfig.json', -}; - const messageId = 'await'; const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 21428ba7256..7106fc461b4 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -3,13 +3,12 @@ import rule from '../../src/rules/no-unnecessary-type-assertion'; import { RuleTester } from '../RuleTester'; const rootDir = path.join(process.cwd(), 'tests/fixtures'); -const parserOptions = { - ecmaVersion: 2015, - tsconfigRootDir: rootDir, - project: './tsconfig.json', -}; const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2015, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); diff --git a/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts b/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts index a472d8619fe..b4c6b4a3de8 100644 --- a/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-function-type.test.ts @@ -2,7 +2,7 @@ import { AST_NODE_TYPES } from '@typescript-eslint/experimental-utils'; import rule from '../../src/rules/prefer-function-type'; import { RuleTester } from '../RuleTester'; -var ruleTester = new RuleTester({ +const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, }, diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 4f38105989e..3910cea5604 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -2,16 +2,14 @@ import rule from '../../src/rules/promise-function-async'; import { RuleTester, getFixturesRootDir } from '../RuleTester'; const rootDir = getFixturesRootDir(); -const parserOptions = { - ecmaVersion: 2018, - tsconfigRootDir: rootDir, - project: './tsconfig.json', -}; - const messageId = 'missingAsync'; const ruleTester = new RuleTester({ - parserOptions, + parserOptions: { + ecmaVersion: 2018, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, parser: '@typescript-eslint/parser', }); diff --git a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts index 94b6520de81..5d65e2f1801 100644 --- a/packages/eslint-plugin/tests/rules/unified-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/unified-signatures.test.ts @@ -5,7 +5,7 @@ import { RuleTester } from '../RuleTester'; // Tests //------------------------------------------------------------------------------ -var ruleTester = new RuleTester({ parser: '@typescript-eslint/parser' }); +const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser' }); ruleTester.run('unified-signatures', rule, { valid: [ diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index 4c29753a048..9809adc05c7 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -1,16 +1,17 @@ /* eslint-disable no-console */ -import { Linter } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import path from 'path'; -import { TSESLint } from '@typescript-eslint/experimental-utils'; import rules from '../src/rules'; interface LinterConfigRules { - [name: string]: Linter.RuleLevel | Linter.RuleLevelAndOptions; + [name: string]: + | TSESLint.Linter.RuleLevel + | TSESLint.Linter.RuleLevelAndOptions; } -interface LinterConfig extends Linter.Config { +interface LinterConfig extends TSESLint.Linter.Config { extends?: string | string[]; plugins?: string[]; } diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 5bafdb42c7d..8d7cb94092f 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -34,6 +34,7 @@ "@typescript-eslint/typescript-estree": "1.9.0" }, "peerDependencies": { + "eslint": "*", "typescript": "*" } } diff --git a/packages/experimental-utils/src/ts-eslint/CLIEngine.ts b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts new file mode 100644 index 00000000000..0a64a3d6734 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts @@ -0,0 +1,90 @@ +/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ + +import { CLIEngine as ESLintCLIEngine } from 'eslint'; +import { Linter } from './Linter'; +import { RuleModule, RuleListener } from './Rule'; + +interface CLIEngine { + version: string; + + executeOnFiles(patterns: string[]): CLIEngine.LintReport; + + resolveFileGlobPatterns(patterns: string[]): string[]; + + getConfigForFile(filePath: string): Linter.Config; + + executeOnText(text: string, filename?: string): CLIEngine.LintReport; + + addPlugin(name: string, pluginObject: any): void; + + isPathIgnored(filePath: string): boolean; + + getFormatter(format?: string): CLIEngine.Formatter; + + getRules< + TMessageIds extends string = any, + TOptions extends readonly any[] = any[], + // for extending base rules + TRuleListener extends RuleListener = RuleListener + >(): Map>; +} + +namespace CLIEngine { + export interface Options { + allowInlineConfig?: boolean; + baseConfig?: false | { [name: string]: any }; + cache?: boolean; + cacheFile?: string; + cacheLocation?: string; + configFile?: string; + cwd?: string; + envs?: string[]; + extensions?: string[]; + fix?: boolean; + globals?: string[]; + ignore?: boolean; + ignorePath?: string; + ignorePattern?: string | string[]; + useEslintrc?: boolean; + parser?: string; + parserOptions?: Linter.ParserOptions; + plugins?: string[]; + rules?: { + [name: string]: Linter.RuleLevel | Linter.RuleLevelAndOptions; + }; + rulePaths?: string[]; + reportUnusedDisableDirectives?: boolean; + } + + export interface LintResult { + filePath: string; + messages: Linter.LintMessage[]; + errorCount: number; + warningCount: number; + fixableErrorCount: number; + fixableWarningCount: number; + output?: string; + source?: string; + } + + export interface LintReport { + results: LintResult[]; + errorCount: number; + warningCount: number; + fixableErrorCount: number; + fixableWarningCount: number; + } + + export type Formatter = (results: LintResult[]) => string; +} + +const CLIEngine = ESLintCLIEngine as { + new (options: CLIEngine.Options): CLIEngine; + + // static methods + getErrorResults(results: CLIEngine.LintResult[]): CLIEngine.LintResult[]; + + outputFixes(report: CLIEngine.LintReport): void; +}; + +export { CLIEngine }; diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts index cff921e048c..dde85a07f2b 100644 --- a/packages/experimental-utils/src/ts-eslint/Linter.ts +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -1,11 +1,13 @@ /* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ import { TSESTree, ParserServices } from '@typescript-eslint/typescript-estree'; +import { Linter as ESLintLinter } from 'eslint'; +import { ParserOptions as TSParserOptions } from './ParserOptions'; import { RuleModule, RuleFix } from './Rule'; import { Scope } from './Scope'; import { SourceCode } from './SourceCode'; -declare class Linter { +interface Linter { version: string; verify( @@ -34,7 +36,10 @@ declare class Linter { defineRule( name: string, - rule: RuleModule, + rule: { + meta?: RuleModule['meta']; + create: RuleModule['create']; + }, ): void; defineRules( @@ -68,18 +73,7 @@ namespace Linter { globals?: { [name: string]: boolean }; } - export interface ParserOptions { - ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 2015 | 2016 | 2017 | 2018; - sourceType?: 'script' | 'module'; - ecmaFeatures?: { - globalReturn?: boolean; - impliedStrict?: boolean; - jsx?: boolean; - experimentalObjectRestSpread?: boolean; - [key: string]: any; - }; - [key: string]: any; - } + export type ParserOptions = TSParserOptions; export interface LintOptions { filename?: string; @@ -129,4 +123,8 @@ namespace Linter { } } +const Linter = ESLintLinter as { + new (): Linter; +}; + export { Linter }; diff --git a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts index d374ac57b91..915e6726172 100644 --- a/packages/experimental-utils/src/ts-eslint/ParserOptions.ts +++ b/packages/experimental-utils/src/ts-eslint/ParserOptions.ts @@ -4,7 +4,7 @@ export interface ParserOptions { range?: boolean; tokens?: boolean; sourceType?: 'script' | 'module'; - ecmaVersion?: number; + ecmaVersion?: 3 | 5 | 6 | 7 | 8 | 9 | 2015 | 2016 | 2017 | 2018; ecmaFeatures?: { globalReturn?: boolean; jsx?: boolean; diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 48162df0867..388f64e99fc 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -105,7 +105,7 @@ type ReportFixFunction = ( fixer: RuleFixer, ) => null | RuleFix | RuleFix[] | IterableIterator; -interface ReportDescriptor { +interface ReportDescriptorBase { /** * The parameters for the message string associated with `messageId`. */ @@ -118,6 +118,8 @@ interface ReportDescriptor { * The messageId which is being reported. */ messageId: TMessageIds; +} +interface ReportDescriptorNodeOptionalLoc { /** * The Node or AST Token which the report is being attached to */ @@ -127,10 +129,20 @@ interface ReportDescriptor { */ loc?: TSESTree.SourceLocation | TSESTree.LineAndColumnData; } +interface ReportDescriptorLocOnly { + /** + * An override of the location of the report + */ + loc: TSESTree.SourceLocation | TSESTree.LineAndColumnData; +} +type ReportDescriptor = ReportDescriptorBase< + TMessageIds +> & + (ReportDescriptorNodeOptionalLoc | ReportDescriptorLocOnly); interface RuleContext< TMessageIds extends string, - TOptions extends Readonly + TOptions extends readonly any[] > { /** * The rule ID. @@ -370,7 +382,7 @@ interface RuleListener { interface RuleModule< TMessageIds extends string, - TOptions extends Readonly, + TOptions extends readonly any[], // for extending base rules TRuleListener extends RuleListener = RuleListener > { diff --git a/packages/experimental-utils/src/ts-eslint/RuleTester.ts b/packages/experimental-utils/src/ts-eslint/RuleTester.ts index ea677806cf3..4478abca8dd 100644 --- a/packages/experimental-utils/src/ts-eslint/RuleTester.ts +++ b/packages/experimental-utils/src/ts-eslint/RuleTester.ts @@ -2,6 +2,7 @@ import { AST_NODE_TYPES, AST_TOKEN_TYPES, } from '@typescript-eslint/typescript-estree'; +import { RuleTester as ESLintRuleTester } from 'eslint'; import { ParserOptions } from './ParserOptions'; import { RuleModule } from './Rule'; @@ -57,16 +58,16 @@ interface RuleTesterConfig { parser: '@typescript-eslint/parser'; parserOptions?: ParserOptions; } -interface RuleTester { - // eslint-disable-next-line @typescript-eslint/no-misused-new - new (config?: RuleTesterConfig): RuleTester; - +declare interface RuleTester { run>( name: string, rule: RuleModule, tests: RunTests, ): void; } +const RuleTester = ESLintRuleTester as { + new (config?: RuleTesterConfig): RuleTester; +}; export { InvalidTestCase, diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts index abf3c3e6e8f..2fb2e0b3cab 100644 --- a/packages/experimental-utils/src/ts-eslint/SourceCode.ts +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -1,50 +1,10 @@ /* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; +import { SourceCode as ESLintSourceCode } from 'eslint'; import { Scope } from './Scope'; -namespace SourceCode { - export interface Program extends TSESTree.Program { - comments: TSESTree.Comment[]; - tokens: TSESTree.Token[]; - } - - export interface Config { - text: string; - ast: Program; - parserServices?: ParserServices; - scopeManager?: Scope.ScopeManager; - visitorKeys?: VisitorKeys; - } - - export interface VisitorKeys { - [nodeType: string]: string[]; - } - - export type FilterPredicate = ( - tokenOrComment: TSESTree.Token | TSESTree.Comment, - ) => boolean; - - export type CursorWithSkipOptions = - | number - | FilterPredicate - | { - includeComments?: boolean; - filter?: FilterPredicate; - skip?: number; - }; - - export type CursorWithCountOptions = - | number - | FilterPredicate - | { - includeComments?: boolean; - filter?: FilterPredicate; - count?: number; - }; -} - -declare class SourceCode { +declare interface SourceCode { text: string; ast: SourceCode.Program; lines: string[]; @@ -54,11 +14,6 @@ declare class SourceCode { visitorKeys: SourceCode.VisitorKeys; tokensAndComments: (TSESTree.Comment | TSESTree.Token)[]; - constructor(text: string, ast: SourceCode.Program); - constructor(config: SourceCode.Config); - - static splitLines(text: string): string[]; - getText( node?: TSESTree.Node, beforeCount?: number, @@ -190,4 +145,53 @@ declare class SourceCode { getCommentsInside(node: TSESTree.Node): TSESTree.Comment[]; } +namespace SourceCode { + export interface Program extends TSESTree.Program { + comments: TSESTree.Comment[]; + tokens: TSESTree.Token[]; + } + + export interface Config { + text: string; + ast: Program; + parserServices?: ParserServices; + scopeManager?: Scope.ScopeManager; + visitorKeys?: VisitorKeys; + } + + export interface VisitorKeys { + [nodeType: string]: string[]; + } + + export type FilterPredicate = ( + tokenOrComment: TSESTree.Token | TSESTree.Comment, + ) => boolean; + + export type CursorWithSkipOptions = + | number + | FilterPredicate + | { + includeComments?: boolean; + filter?: FilterPredicate; + skip?: number; + }; + + export type CursorWithCountOptions = + | number + | FilterPredicate + | { + includeComments?: boolean; + filter?: FilterPredicate; + count?: number; + }; +} + +const SourceCode = ESLintSourceCode as { + new (text: string, ast: SourceCode.Program): SourceCode; + new (config: SourceCode.Config): SourceCode; + + // static methods + splitLines(text: string): string[]; +}; + export { SourceCode }; diff --git a/packages/experimental-utils/tsconfig.build.json b/packages/experimental-utils/tsconfig.build.json index 0ce1565b0d0..c052e521130 100644 --- a/packages/experimental-utils/tsconfig.build.json +++ b/packages/experimental-utils/tsconfig.build.json @@ -5,5 +5,5 @@ "rootDir": "./src", "resolveJsonModule": true }, - "include": ["src"] + "include": ["src", "typings"] } diff --git a/packages/experimental-utils/typings/eslint.d.ts b/packages/experimental-utils/typings/eslint.d.ts new file mode 100644 index 00000000000..a32b469a977 --- /dev/null +++ b/packages/experimental-utils/typings/eslint.d.ts @@ -0,0 +1,15 @@ +/* +We intentionally do not include @types/eslint. + +This is to ensure that nobody accidentally uses those incorrect types +instead of the ones declared within this package +*/ + +declare module 'eslint' { + const Linter: unknown; + const RuleTester: unknown; + const SourceCode: unknown; + const CLIEngine: unknown; + + export { Linter, RuleTester, SourceCode, CLIEngine }; +} diff --git a/packages/parser/src/parser-options.ts b/packages/parser/src/parser-options.ts index d374ac57b91..9848d54ba70 100644 --- a/packages/parser/src/parser-options.ts +++ b/packages/parser/src/parser-options.ts @@ -1,21 +1,3 @@ -export interface ParserOptions { - loc?: boolean; - comment?: boolean; - range?: boolean; - tokens?: boolean; - sourceType?: 'script' | 'module'; - ecmaVersion?: number; - ecmaFeatures?: { - globalReturn?: boolean; - jsx?: boolean; - }; - // ts-estree specific - filePath?: string; - project?: string | string[]; - useJSXTextNode?: boolean; - errorOnUnknownASTType?: boolean; - errorOnTypeScriptSyntacticAndSemanticIssues?: boolean; - tsconfigRootDir?: string; - extraFileExtensions?: string[]; - warnOnUnsupportedTypeScriptVersion?: boolean; -} +import { TSESLint } from '@typescript-eslint/experimental-utils'; + +export type ParserOptions = TSESLint.ParserOptions; diff --git a/packages/parser/tests/lib/basics.ts b/packages/parser/tests/lib/basics.ts index 042e3fd731c..4b237a7dc2a 100644 --- a/packages/parser/tests/lib/basics.ts +++ b/packages/parser/tests/lib/basics.ts @@ -1,4 +1,4 @@ -import { Linter } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import glob from 'glob'; import * as parser from '../../src/parser'; @@ -24,11 +24,11 @@ describe('basics', () => { }); it('https://github.com/eslint/typescript-eslint-parser/issues/476', () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); const code = ` export const Price: React.SFC = function Price(props) {} `; - const config: Linter.Config = { + const config: TSESLint.Linter.Config = { parser: '@typescript-eslint/parser', rules: { test: 'error', @@ -37,15 +37,15 @@ export const Price: React.SFC = function Price(props) {} linter.defineParser('@typescript-eslint/parser', parser); linter.defineRule('test', { - create(context: any) { + create(context) { return { - TSTypeReference(node: any) { + TSTypeReference(node) { const name = context.getSourceCode().getText(node.typeName); context.report({ node, message: 'called on {{name}}', data: { name }, - }); + } as any); }, }; }, diff --git a/packages/parser/tests/lib/parser.ts b/packages/parser/tests/lib/parser.ts index c3c205509dd..9545633cd6e 100644 --- a/packages/parser/tests/lib/parser.ts +++ b/packages/parser/tests/lib/parser.ts @@ -1,3 +1,4 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; import * as typescriptESTree from '@typescript-eslint/typescript-estree'; import { parse, parseForESLint, Syntax } from '../../src/parser'; import * as scope from '../../src/analyze-scope'; @@ -35,13 +36,13 @@ describe('parser', () => { it('parseAndGenerateServices() should be called with options', () => { const code = 'const valid = true;'; const spy = jest.spyOn(typescriptESTree, 'parseAndGenerateServices'); - const config = { + const config: TSESLint.ParserOptions = { loc: false, comment: false, range: false, tokens: false, sourceType: 'module' as 'module', - ecmaVersion: 10, + ecmaVersion: 2018, ecmaFeatures: { globalReturn: false, jsx: false, diff --git a/packages/parser/tests/lib/tsx.ts b/packages/parser/tests/lib/tsx.ts index eed70b17e8d..21937886b5d 100644 --- a/packages/parser/tests/lib/tsx.ts +++ b/packages/parser/tests/lib/tsx.ts @@ -1,4 +1,4 @@ -import { Linter } from 'eslint'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import glob from 'glob'; import * as parser from '../../src/parser'; @@ -31,7 +31,7 @@ describe('TSX', () => { }); describe("if the filename ends with '.tsx', enable jsx option automatically.", () => { - const linter = new Linter(); + const linter = new TSESLint.Linter(); linter.defineParser('@typescript-eslint/parser', parser); it('filePath was not provided', () => { diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index e0e535c774a..f8151e1b268 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -102,7 +102,7 @@ export class Converter { this.allowPattern = allowPattern; } - let result = this.convertNode(node as TSNode, parent || node.parent); + const result = this.convertNode(node as TSNode, parent || node.parent); this.registerTSNodeInNodeMap(node, result); @@ -1390,7 +1390,7 @@ export class Converter { case SyntaxKind.ClassDeclaration: case SyntaxKind.ClassExpression: { const heritageClauses = node.heritageClauses || []; - let classNodeType = + const classNodeType = node.kind === SyntaxKind.ClassDeclaration ? AST_NODE_TYPES.ClassDeclaration : AST_NODE_TYPES.ClassExpression; diff --git a/packages/typescript-estree/src/tsconfig-parser.ts b/packages/typescript-estree/src/tsconfig-parser.ts index 44e1f13b28b..641af07a77a 100644 --- a/packages/typescript-estree/src/tsconfig-parser.ts +++ b/packages/typescript-estree/src/tsconfig-parser.ts @@ -82,7 +82,7 @@ export function calculateProjectParserOptions( watchCallback(filePath, ts.FileWatcherEventKind.Changed); } - for (let rawTsconfigPath of extra.projects) { + for (const rawTsconfigPath of extra.projects) { const tsconfigPath = getTsconfigPath(rawTsconfigPath, extra); const existingWatch = knownWatchProgramMap.get(tsconfigPath); diff --git a/yarn.lock b/yarn.lock index 6541649af58..4f4a0d81f56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1187,19 +1187,6 @@ resolved "https://registry.yarnpkg.com/@types/eslint-visitor-keys/-/eslint-visitor-keys-1.0.0.tgz#1ee30d79544ca84d68d4b3cdb0af4f205663dd2d" integrity sha512-OCutwjDZ4aFS6PB1UZ988C4YgwlBHJd6wCeQqaLdmadZ/7e+w79+hbMUFC1QXDNCmdyoRfAFdm0RypzwR+Qpag== -"@types/eslint@^4.16.3": - version "4.16.6" - resolved "https://registry.yarnpkg.com/@types/eslint/-/eslint-4.16.6.tgz#96d4ecddbea618ab0b55eaf0dffedf387129b06c" - integrity sha512-GL7tGJig55FeclpOytU7nCCqtR143jBoC7AUdH0DO9xBSIFiNNUFCY/S3KNWsHeQJuU3hjw/OC1+kRTFNXqUZQ== - dependencies: - "@types/estree" "*" - "@types/json-schema" "*" - -"@types/estree@*": - version "0.0.39" - resolved "https://registry.yarnpkg.com/@types/estree/-/estree-0.0.39.tgz#e177e699ee1b8c22d23174caaa7422644389509f" - integrity sha512-EYNwp3bU+98cpU4lAWYYL7Zz+2gryWH1qbdDTidVd6hkiR6weksdbMadyXKXNPEkQFhXM+hVO9ZygomHXp+AIw== - "@types/events@*": version "3.0.0" resolved "https://registry.yarnpkg.com/@types/events/-/events-3.0.0.tgz#2862f3f58a9a7f7c3e78d79f130dd4d71c25c2a7" @@ -1231,7 +1218,7 @@ dependencies: "@types/jest-diff" "*" -"@types/json-schema@*": +"@types/json-schema@^7.0.3": version "7.0.3" resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.3.tgz#bdfd69d61e464dcc81b25159c270d75a73c1a636" integrity sha512-Il2DtDVRGDcqjDtE+rF8iqg1CArehSK84HZJCT7AMITlyXRBpuPhqGLDQMowraqqu1coEaimg4ZOqggt6L6L+A== From 035b066ea8e919598ba9200bcf1319558cbe1481 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 15 May 2019 18:06:58 -0700 Subject: [PATCH 21/41] feat(experimental-utils): Add types for eslint-scope (#527) --- packages/eslint-plugin/tsconfig.json | 9 +- packages/experimental-utils/package.json | 4 +- packages/experimental-utils/src/index.ts | 3 +- .../src/ts-eslint-scope/Definition.ts | 39 ++ .../src/ts-eslint-scope/Options.ts | 21 + .../src/ts-eslint-scope/PatternVisitor.ts | 38 ++ .../src/ts-eslint-scope/Reference.ts | 27 + .../src/ts-eslint-scope/Referencer.ts | 80 +++ .../src/ts-eslint-scope/Scope.ts | 193 +++++++ .../src/ts-eslint-scope/ScopeManager.ts | 57 ++ .../src/ts-eslint-scope/Variable.ts | 18 + .../src/ts-eslint-scope/analyze.ts | 19 + .../src/ts-eslint-scope/index.ts | 12 + .../typings/eslint-scope.d.ts | 63 +++ packages/parser/package.json | 3 +- packages/parser/src/analyze-scope.ts | 85 ++- packages/parser/src/parser.ts | 4 +- packages/parser/src/scope/scope-manager.ts | 14 +- packages/parser/src/scope/scopes.ts | 11 +- packages/parser/typings/eslint-scope.d.ts | 494 ------------------ packages/parser/typings/eslint.d.ts | 14 + 21 files changed, 662 insertions(+), 546 deletions(-) create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Definition.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Options.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Reference.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Referencer.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Scope.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/Variable.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/analyze.ts create mode 100644 packages/experimental-utils/src/ts-eslint-scope/index.ts create mode 100644 packages/experimental-utils/typings/eslint-scope.d.ts delete mode 100644 packages/parser/typings/eslint-scope.d.ts create mode 100644 packages/parser/typings/eslint.d.ts diff --git a/packages/eslint-plugin/tsconfig.json b/packages/eslint-plugin/tsconfig.json index fc93e91c26d..fb7e21da237 100644 --- a/packages/eslint-plugin/tsconfig.json +++ b/packages/eslint-plugin/tsconfig.json @@ -1,11 +1,4 @@ { "extends": "./tsconfig.build.json", - "include": [ - "src", - "typings", - // include the parser's ambient typings because the parser exports them in its type defs - "../parser/typings", - "tests", - "tools" - ] + "include": ["src", "typings", "tests", "tools"] } diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 8d7cb94092f..9d51a32c4fd 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -28,10 +28,12 @@ "prebuild": "npm run clean", "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", + "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.9.0" + "@typescript-eslint/typescript-estree": "1.9.0", + "eslint-scope": "^4.0.0" }, "peerDependencies": { "eslint": "*", diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts index 8b3a7f039ff..93b3831817c 100644 --- a/packages/experimental-utils/src/index.ts +++ b/packages/experimental-utils/src/index.ts @@ -1,7 +1,8 @@ import * as ESLintUtils from './eslint-utils'; import * as TSESLint from './ts-eslint'; +import * as TSESLintScope from './ts-eslint-scope'; -export { ESLintUtils, TSESLint }; +export { ESLintUtils, TSESLint, TSESLintScope }; // for convenience's sake - export the types directly from here so consumers // don't need to reference/install both packages in their code diff --git a/packages/experimental-utils/src/ts-eslint-scope/Definition.ts b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts new file mode 100644 index 00000000000..b2f4b91383e --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Definition.ts @@ -0,0 +1,39 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { + Definition as ESLintDefinition, + ParameterDefinition as ESLintParameterDefinition, +} from 'eslint-scope/lib/definition'; + +interface Definition { + type: string; + name: TSESTree.BindingName; + node: TSESTree.Node; + parent?: TSESTree.Node | null; + index?: number | null; + kind?: string | null; + rest?: boolean; +} +interface DefinitionConstructor { + new ( + type: string, + name: TSESTree.BindingName | TSESTree.PropertyName, + node: TSESTree.Node, + parent?: TSESTree.Node | null, + index?: number | null, + kind?: string | null, + ): Definition; +} +const Definition = ESLintDefinition as DefinitionConstructor; + +// eslint-disable-next-line @typescript-eslint/no-empty-interface +interface ParameterDefinition extends Definition {} +const ParameterDefinition = ESLintParameterDefinition as DefinitionConstructor & { + new ( + name: TSESTree.Node, + node: TSESTree.Node, + index?: number | null, + rest?: boolean, + ): ParameterDefinition; +}; + +export { Definition, ParameterDefinition }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Options.ts b/packages/experimental-utils/src/ts-eslint-scope/Options.ts new file mode 100644 index 00000000000..f06fe4e42e8 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Options.ts @@ -0,0 +1,21 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +type PatternVisitorCallback = ( + pattern: TSESTree.Identifier, + info: { + rest: boolean; + topLevel: boolean; + assignments: TSESTree.AssignmentPattern[]; + }, +) => void; + +interface PatternVisitorOptions { + processRightHandNodes?: boolean; +} + +interface Visitor { + visitChildren(node?: T): void; + visit(node?: T): void; +} + +export { PatternVisitorCallback, PatternVisitorOptions, Visitor }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts new file mode 100644 index 00000000000..a31645b1228 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/PatternVisitor.ts @@ -0,0 +1,38 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintPatternVisitor from 'eslint-scope/lib/pattern-visitor'; +import { ScopeManager } from './ScopeManager'; +import { + PatternVisitorCallback, + PatternVisitorOptions, + Visitor, +} from './Options'; + +interface PatternVisitor extends Visitor { + options: any; + scopeManager: ScopeManager; + parent?: TSESTree.Node; + rightHandNodes: TSESTree.Node[]; + + Identifier(pattern: TSESTree.Node): void; + Property(property: TSESTree.Node): void; + ArrayPattern(pattern: TSESTree.Node): void; + AssignmentPattern(pattern: TSESTree.Node): void; + RestElement(pattern: TSESTree.Node): void; + MemberExpression(node: TSESTree.Node): void; + SpreadElement(node: TSESTree.Node): void; + ArrayExpression(node: TSESTree.Node): void; + AssignmentExpression(node: TSESTree.Node): void; + CallExpression(node: TSESTree.Node): void; +} +const PatternVisitor = ESLintPatternVisitor as { + new ( + options: PatternVisitorOptions, + rootPattern: any, + callback: PatternVisitorCallback, + ): PatternVisitor; + + // static methods + isPattern(node: TSESTree.Node): boolean; +}; + +export { PatternVisitor }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Reference.ts b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts new file mode 100644 index 00000000000..15afc7dcdc1 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Reference.ts @@ -0,0 +1,27 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintReference from 'eslint-scope/lib/reference'; +import { Scope } from './Scope'; +import { Variable } from './Variable'; + +interface Reference { + identifier: TSESTree.Identifier; + from: Scope; + resolved: Variable | null; + writeExpr: TSESTree.Node | null; + init: boolean; + + isWrite(): boolean; + isRead(): boolean; + isWriteOnly(): boolean; + isReadOnly(): boolean; + isReadWrite(): boolean; +} +const Reference = ESLintReference as { + new (): Reference; + + READ: 0x1; + WRITE: 0x2; + RW: 0x3; +}; + +export { Reference }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts new file mode 100644 index 00000000000..b430047c01e --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Referencer.ts @@ -0,0 +1,80 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintReferencer from 'eslint-scope/lib/referencer'; +import { + PatternVisitorCallback, + PatternVisitorOptions, + Visitor, +} from './Options'; +import { Scope } from './Scope'; +import { ScopeManager } from './ScopeManager'; + +interface Referencer extends Visitor { + isInnerMethodDefinition: boolean; + options: any; + scopeManager: SM; + parent?: TSESTree.Node; + + currentScope(): Scope; + close(node: TSESTree.Node): void; + pushInnerMethodDefinition(isInnerMethodDefinition: boolean): boolean; + popInnerMethodDefinition(isInnerMethodDefinition: boolean): void; + + referencingDefaultValue( + pattern: any, + assignments: any, + maybeImplicitGlobal: any, + init: boolean, + ): void; + visitPattern( + node: TSESTree.Node, + options: PatternVisitorOptions, + callback: PatternVisitorCallback, + ): void; + visitFunction(node: TSESTree.Node): void; + visitClass(node: TSESTree.Node): void; + visitProperty(node: TSESTree.Node): void; + visitForIn(node: TSESTree.Node): void; + visitVariableDeclaration( + variableTargetScope: any, + type: any, + node: TSESTree.Node, + index: any, + ): void; + + AssignmentExpression(node: TSESTree.Node): void; + CatchClause(node: TSESTree.Node): void; + Program(node: TSESTree.Node): void; + Identifier(node: TSESTree.Node): void; + UpdateExpression(node: TSESTree.Node): void; + MemberExpression(node: TSESTree.Node): void; + Property(node: TSESTree.Node): void; + MethodDefinition(node: TSESTree.Node): void; + BreakStatement(): void; + ContinueStatement(): void; + LabeledStatement(node: TSESTree.Node): void; + ForStatement(node: TSESTree.Node): void; + ClassExpression(node: TSESTree.Node): void; + ClassDeclaration(node: TSESTree.Node): void; + CallExpression(node: TSESTree.Node): void; + BlockStatement(node: TSESTree.Node): void; + ThisExpression(): void; + WithStatement(node: TSESTree.Node): void; + VariableDeclaration(node: TSESTree.Node): void; + SwitchStatement(node: TSESTree.Node): void; + FunctionDeclaration(node: TSESTree.Node): void; + FunctionExpression(node: TSESTree.Node): void; + ForOfStatement(node: TSESTree.Node): void; + ForInStatement(node: TSESTree.Node): void; + ArrowFunctionExpression(node: TSESTree.Node): void; + ImportDeclaration(node: TSESTree.Node): void; + visitExportDeclaration(node: TSESTree.Node): void; + ExportDeclaration(node: TSESTree.Node): void; + ExportNamedDeclaration(node: TSESTree.Node): void; + ExportSpecifier(node: TSESTree.Node): void; + MetaProperty(): void; +} +const Referencer = ESLintReferencer as { + new (options: any, scopeManager: SM): Referencer; +}; + +export { Referencer }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Scope.ts b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts new file mode 100644 index 00000000000..71b8dbf42a4 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Scope.ts @@ -0,0 +1,193 @@ +/* eslint-disable @typescript-eslint/no-empty-interface */ + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { + Scope as ESLintScope, + GlobalScope as ESLintGlobalScope, + ModuleScope as ESLintModuleScope, + FunctionExpressionNameScope as ESLintFunctionExpressionNameScope, + CatchScope as ESLintCatchScope, + WithScope as ESLintWithScope, + BlockScope as ESLintBlockScope, + SwitchScope as ESLintSwitchScope, + FunctionScope as ESLintFunctionScope, + ForScope as ESLintForScope, + ClassScope as ESLintClassScope, +} from 'eslint-scope/lib/scope'; +import { Definition } from './Definition'; +import { Reference } from './Reference'; +import { ScopeManager } from './ScopeManager'; +import { Variable } from './Variable'; + +type ScopeType = + | 'block' + | 'catch' + | 'class' + | 'for' + | 'function' + | 'function-expression-name' + | 'global' + | 'module' + | 'switch' + | 'with' + | 'TDZ' + | 'enum' + | 'empty-function'; + +interface Scope { + type: ScopeType; + isStrict: boolean; + upper: Scope | null; + childScopes: Scope[]; + variableScope: Scope; + block: TSESTree.Node; + variables: Variable[]; + set: Map; + references: Reference[]; + through: Reference[]; + thisFound?: boolean; + functionExpressionScope: boolean; + + __shouldStaticallyClose(scopeManager: ScopeManager): boolean; + __shouldStaticallyCloseForGlobal(ref: any): boolean; + __staticCloseRef(ref: any): void; + __dynamicCloseRef(ref: any): void; + __globalCloseRef(ref: any): void; + __close(scopeManager: ScopeManager): Scope; + __isValidResolution(ref: any, variable: any): boolean; + __resolve(ref: any): boolean; + __delegateToUpperScope(ref: any): void; + __addDeclaredVariablesOfNode(variable: any, node: TSESTree.Node): void; + __defineGeneric( + name: any, + set: any, + variables: any, + node: any, + def: Definition, + ): void; + + __define(node: TSESTree.Node, def: Definition): void; + + __referencing( + node: TSESTree.Node, + assign: number, + writeExpr: TSESTree.Node, + maybeImplicitGlobal: any, + partial: any, + init: any, + ): void; + + __detectEval(): void; + __detectThis(): void; + __isClosed(): boolean; + /** + * returns resolved {Reference} + * @method Scope#resolve + * @param {Espree.Identifier} ident - identifier to be resolved. + * @returns {Reference} reference + */ + resolve(ident: TSESTree.Node): Reference; + + /** + * returns this scope is static + * @method Scope#isStatic + * @returns {boolean} static + */ + isStatic(): boolean; + + /** + * returns this scope has materialized arguments + * @method Scope#isArgumentsMaterialized + * @returns {boolean} arguemnts materialized + */ + isArgumentsMaterialized(): boolean; + + /** + * returns this scope has materialized `this` reference + * @method Scope#isThisMaterialized + * @returns {boolean} this materialized + */ + isThisMaterialized(): boolean; + + isUsedName(name: any): boolean; +} +interface ScopeConstructor { + new ( + scopeManager: ScopeManager, + type: ScopeType, + upperScope: Scope | null, + block: TSESTree.Node | null, + isMethodDefinition: boolean, + ): Scope; +} +const Scope = ESLintScope as ScopeConstructor; + +interface ScopeChildConstructorWithUpperScope { + new ( + scopeManager: ScopeManager, + upperScope: Scope, + block: TSESTree.Node | null, + ): T; +} + +interface GlobalScope extends Scope {} +const GlobalScope = ESLintGlobalScope as ScopeConstructor & { + new (scopeManager: ScopeManager, block: TSESTree.Node | null): GlobalScope; +}; + +interface ModuleScope extends Scope {} +const ModuleScope = ESLintModuleScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface FunctionExpressionNameScope extends Scope {} +const FunctionExpressionNameScope = ESLintFunctionExpressionNameScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface CatchScope extends Scope {} +const CatchScope = ESLintCatchScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface WithScope extends Scope {} +const WithScope = ESLintWithScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface BlockScope extends Scope {} +const BlockScope = ESLintBlockScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface SwitchScope extends Scope {} +const SwitchScope = ESLintSwitchScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface FunctionScope extends Scope {} +const FunctionScope = ESLintFunctionScope as ScopeConstructor & { + new ( + scopeManager: ScopeManager, + upperScope: Scope, + block: TSESTree.Node | null, + isMethodDefinition: boolean, + ): FunctionScope; +}; + +interface ForScope extends Scope {} +const ForScope = ESLintForScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +interface ClassScope extends Scope {} +const ClassScope = ESLintClassScope as ScopeConstructor & + ScopeChildConstructorWithUpperScope; + +export { + ScopeType, + Scope, + GlobalScope, + ModuleScope, + FunctionExpressionNameScope, + CatchScope, + WithScope, + BlockScope, + SwitchScope, + FunctionScope, + ForScope, + ClassScope, +}; diff --git a/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts new file mode 100644 index 00000000000..e90c3cf4b11 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/ScopeManager.ts @@ -0,0 +1,57 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintScopeManager from 'eslint-scope/lib/scope-manager'; +import { Scope } from './Scope'; +import { Variable } from './Variable'; + +interface ScopeManagerOptions { + directive?: boolean; + optimistic?: boolean; + ignoreEval?: boolean; + nodejsScope?: boolean; + sourceType?: 'module' | 'script'; + impliedStrict?: boolean; + ecmaVersion?: number; +} + +interface ScopeManager { + __options: ScopeManagerOptions; + __currentScope: Scope; + scopes: Scope[]; + globalScope: Scope; + + __useDirective(): boolean; + __isOptimistic(): boolean; + __ignoreEval(): boolean; + __isNodejsScope(): boolean; + isModule(): boolean; + isImpliedStrict(): boolean; + isStrictModeSupported(): boolean; + + // Returns appropriate scope for this node. + __get(node: TSESTree.Node): Scope; + getDeclaredVariables(node: TSESTree.Node): Variable[]; + acquire(node: TSESTree.Node, inner?: boolean): Scope | null; + acquireAll(node: TSESTree.Node): Scope | null; + release(node: TSESTree.Node, inner?: boolean): Scope | null; + attach(): void; + detach(): void; + + __nestScope(scope: Scope): Scope; + __nestGlobalScope(node: TSESTree.Node): Scope; + __nestBlockScope(node: TSESTree.Node): Scope; + __nestFunctionScope(node: TSESTree.Node, isMethodDefinition: boolean): Scope; + __nestForScope(node: TSESTree.Node): Scope; + __nestCatchScope(node: TSESTree.Node): Scope; + __nestWithScope(node: TSESTree.Node): Scope; + __nestClassScope(node: TSESTree.Node): Scope; + __nestSwitchScope(node: TSESTree.Node): Scope; + __nestModuleScope(node: TSESTree.Node): Scope; + __nestFunctionExpressionNameScope(node: TSESTree.Node): Scope; + + __isES6(): boolean; +} +const ScopeManager = ESLintScopeManager as { + new (options: ScopeManagerOptions): ScopeManager; +}; + +export { ScopeManager, ScopeManagerOptions }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/Variable.ts b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts new file mode 100644 index 00000000000..306d5bfca49 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/Variable.ts @@ -0,0 +1,18 @@ +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import ESLintVariable from 'eslint-scope/lib/variable'; +import { Reference } from './Reference'; +import { Definition } from './Definition'; + +interface Variable { + name: string; + identifiers: TSESTree.Identifier[]; + references: Reference[]; + defs: Definition[]; + eslintUsed?: boolean; +} + +const Variable = ESLintVariable as { + new (): Variable; +}; + +export { Variable }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/analyze.ts b/packages/experimental-utils/src/ts-eslint-scope/analyze.ts new file mode 100644 index 00000000000..c4ab4514c8c --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/analyze.ts @@ -0,0 +1,19 @@ +import { analyze as ESLintAnalyze } from 'eslint-scope'; +import { ScopeManager } from './ScopeManager'; + +interface AnalysisOptions { + optimistic?: boolean; + directive?: boolean; + ignoreEval?: boolean; + nodejsScope?: boolean; + impliedStrict?: boolean; + fallback?: string | ((node: {}) => string[]); + sourceType?: 'script' | 'module'; + ecmaVersion?: number; +} +const analyze = ESLintAnalyze as ( + ast: {}, + options?: AnalysisOptions, +) => ScopeManager; + +export { analyze, AnalysisOptions }; diff --git a/packages/experimental-utils/src/ts-eslint-scope/index.ts b/packages/experimental-utils/src/ts-eslint-scope/index.ts new file mode 100644 index 00000000000..d713845f9f4 --- /dev/null +++ b/packages/experimental-utils/src/ts-eslint-scope/index.ts @@ -0,0 +1,12 @@ +import { version as ESLintVersion } from 'eslint-scope'; + +export * from './analyze'; +export * from './Definition'; +export * from './Options'; +export * from './PatternVisitor'; +export * from './Reference'; +export * from './Referencer'; +export * from './Scope'; +export * from './ScopeManager'; +export * from './Variable'; +export const version: string = ESLintVersion; diff --git a/packages/experimental-utils/typings/eslint-scope.d.ts b/packages/experimental-utils/typings/eslint-scope.d.ts new file mode 100644 index 00000000000..7b4d0bc1b2e --- /dev/null +++ b/packages/experimental-utils/typings/eslint-scope.d.ts @@ -0,0 +1,63 @@ +/* +We intentionally do not include @types/eslint-scope. + +This is to ensure that nobody accidentally uses those incorrect types +instead of the ones declared within this package +*/ + +declare module 'eslint-scope/lib/variable' { + const Variable: unknown; + export = Variable; +} +declare module 'eslint-scope/lib/definition' { + const Definition: unknown; + const ParameterDefinition: unknown; + export { Definition, ParameterDefinition }; +} +declare module 'eslint-scope/lib/pattern-visitor' { + const PatternVisitor: unknown; + export = PatternVisitor; +} +declare module 'eslint-scope/lib/referencer' { + const Referencer: unknown; + export = Referencer; +} +declare module 'eslint-scope/lib/scope' { + const Scope: unknown; + const GlobalScope: unknown; + const ModuleScope: unknown; + const FunctionExpressionNameScope: unknown; + const CatchScope: unknown; + const WithScope: unknown; + const BlockScope: unknown; + const SwitchScope: unknown; + const FunctionScope: unknown; + const ForScope: unknown; + const ClassScope: unknown; + export { + Scope, + GlobalScope, + ModuleScope, + FunctionExpressionNameScope, + CatchScope, + WithScope, + BlockScope, + SwitchScope, + FunctionScope, + ForScope, + ClassScope, + }; +} +declare module 'eslint-scope/lib/reference' { + const Reference: unknown; + export = Reference; +} +declare module 'eslint-scope/lib/scope-manager' { + const ScopeManager: unknown; + export = ScopeManager; +} +declare module 'eslint-scope' { + const version: string; + const analyze: unknown; + export { analyze, version }; +} diff --git a/packages/parser/package.json b/packages/parser/package.json index 0389785b401..d33fa890f50 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -37,13 +37,12 @@ "eslint": "^5.0.0" }, "dependencies": { + "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/experimental-utils": "1.9.0", "@typescript-eslint/typescript-estree": "1.9.0", - "eslint-scope": "^4.0.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@types/eslint-visitor-keys": "^1.0.0", "@typescript-eslint/shared-fixtures": "1.9.0" } } diff --git a/packages/parser/src/analyze-scope.ts b/packages/parser/src/analyze-scope.ts index b131104ce87..165d560583f 100644 --- a/packages/parser/src/analyze-scope.ts +++ b/packages/parser/src/analyze-scope.ts @@ -1,12 +1,8 @@ -import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; -import { Definition, ParameterDefinition } from 'eslint-scope/lib/definition'; import { - PatternVisitorCallback, - PatternVisitorOptions, -} from 'eslint-scope/lib/options'; -import OriginalPatternVisitor from 'eslint-scope/lib/pattern-visitor'; -import Reference from 'eslint-scope/lib/reference'; -import OriginalReferencer from 'eslint-scope/lib/referencer'; + TSESTree, + TSESLintScope, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; import { getKeys as fallback } from 'eslint-visitor-keys'; import { ParserOptions } from './parser-options'; @@ -30,11 +26,11 @@ function overrideDefine(define: any) { }; } -class PatternVisitor extends OriginalPatternVisitor { +class PatternVisitor extends TSESLintScope.PatternVisitor { constructor( - options: PatternVisitorOptions, + options: TSESLintScope.PatternVisitorOptions, rootPattern: any, - callback: PatternVisitorCallback, + callback: TSESLintScope.PatternVisitorCallback, ) { super(options, rootPattern, callback); } @@ -87,7 +83,7 @@ class PatternVisitor extends OriginalPatternVisitor { } } -class Referencer extends OriginalReferencer { +class Referencer extends TSESLintScope.Referencer { protected typeMode: boolean; constructor(options: any, scopeManager: ScopeManager) { @@ -103,8 +99,8 @@ class Referencer extends OriginalReferencer { */ visitPattern( node: T, - options: PatternVisitorOptions, - callback: PatternVisitorCallback, + options: TSESLintScope.PatternVisitorOptions, + callback: TSESLintScope.PatternVisitorCallback, ): void { if (!node) { return; @@ -143,7 +139,14 @@ class Referencer extends OriginalReferencer { if (type === 'FunctionDeclaration' && id) { upperScope.__define( id, - new Definition('FunctionName', id, node, null, null, null), + new TSESLintScope.Definition( + 'FunctionName', + id, + node, + null, + null, + null, + ), ); // Remove overload definition to avoid confusion of no-redeclare rule. @@ -183,7 +186,12 @@ class Referencer extends OriginalReferencer { ) { innerScope.__define( pattern, - new ParameterDefinition(pattern, node, i, info.rest), + new TSESLintScope.ParameterDefinition( + pattern, + node, + i, + info.rest, + ), ); this.referencingDefaultValue(pattern, info.assignments, null, true); } @@ -344,7 +352,14 @@ class Referencer extends OriginalReferencer { if (!existed) { upperScope.__define( id, - new Definition('FunctionName', id, node, null, null, null), + new TSESLintScope.Definition( + 'FunctionName', + id, + node, + null, + null, + null, + ), ); } } @@ -364,7 +379,7 @@ class Referencer extends OriginalReferencer { (pattern, info) => { innerScope.__define( pattern, - new ParameterDefinition(pattern, node, i, info.rest), + new TSESLintScope.ParameterDefinition(pattern, node, i, info.rest), ); // Set `variable.eslintUsed` to tell ESLint that the variable is used. @@ -657,7 +672,7 @@ class Referencer extends OriginalReferencer { const scope = this.currentScope(); if (id) { - scope.__define(id, new Definition('EnumName', id, node)); + scope.__define(id, new TSESLintScope.Definition('EnumName', id, node)); } scopeManager.__nestEnumScope(node); @@ -677,9 +692,19 @@ class Referencer extends OriginalReferencer { const { id, initializer } = node; const scope = this.currentScope(); - scope.__define(id, new Definition('EnumMemberName', id, node)); + scope.__define( + id, + new TSESLintScope.Definition('EnumMemberName', id, node), + ); if (initializer) { - scope.__referencing(id, Reference.WRITE, initializer, null, false, true); + scope.__referencing( + id, + TSESLintScope.Reference.WRITE, + initializer, + null, + false, + true, + ); this.visit(initializer); } } @@ -700,7 +725,14 @@ class Referencer extends OriginalReferencer { if (id && id.type === 'Identifier') { scope.__define( id, - new Definition('NamespaceName', id, node, null, null, null), + new TSESLintScope.Definition( + 'NamespaceName', + id, + node, + null, + null, + null, + ), ); } this.visit(body); @@ -738,7 +770,14 @@ class Referencer extends OriginalReferencer { if (id && id.type === 'Identifier') { this.currentScope().__define( id, - new Definition('ImportBinding', id, node, null, null, null), + new TSESLintScope.Definition( + 'ImportBinding', + id, + node, + null, + null, + null, + ), ); } this.visit(moduleReference); diff --git a/packages/parser/src/parser.ts b/packages/parser/src/parser.ts index 71478ee2ad6..edc5d12bf67 100644 --- a/packages/parser/src/parser.ts +++ b/packages/parser/src/parser.ts @@ -88,12 +88,12 @@ export function parseForESLint( ast.sourceType = options.sourceType; traverser.traverse(ast, { - enter(node: any) { + enter(node) { switch (node.type) { // Function#body cannot be null in ESTree spec. case 'FunctionExpression': if (!node.body) { - node.type = `TSEmptyBody${node.type}` as AST_NODE_TYPES; + node.type = `TSEmptyBody${node.type}` as any; } break; // no default diff --git a/packages/parser/src/scope/scope-manager.ts b/packages/parser/src/scope/scope-manager.ts index 7b7e53c9e84..648e24b77f8 100644 --- a/packages/parser/src/scope/scope-manager.ts +++ b/packages/parser/src/scope/scope-manager.ts @@ -1,18 +1,14 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import EslintScopeManager, { - ScopeManagerOptions, -} from 'eslint-scope/lib/scope-manager'; -import { Scope } from 'eslint-scope/lib/scope'; +import { TSESTree, TSESLintScope } from '@typescript-eslint/experimental-utils'; import { EmptyFunctionScope, EnumScope } from './scopes'; /** * based on eslint-scope */ -export class ScopeManager extends EslintScopeManager { - scopes!: Scope[]; - globalScope!: Scope; +export class ScopeManager extends TSESLintScope.ScopeManager { + scopes!: TSESLintScope.Scope[]; + globalScope!: TSESLintScope.Scope; - constructor(options: ScopeManagerOptions) { + constructor(options: TSESLintScope.ScopeManagerOptions) { super(options); } diff --git a/packages/parser/src/scope/scopes.ts b/packages/parser/src/scope/scopes.ts index 9dff225a721..4ddaa297d53 100644 --- a/packages/parser/src/scope/scopes.ts +++ b/packages/parser/src/scope/scopes.ts @@ -1,12 +1,11 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import { Scope } from 'eslint-scope/lib/scope'; +import { TSESTree, TSESLintScope } from '@typescript-eslint/experimental-utils'; import { ScopeManager } from './scope-manager'; /** The scope class for enum. */ -export class EnumScope extends Scope { +export class EnumScope extends TSESLintScope.Scope { constructor( scopeManager: ScopeManager, - upperScope: Scope, + upperScope: TSESLintScope.Scope, block: TSESTree.TSEnumDeclaration | null, ) { super(scopeManager, 'enum', upperScope, block, false); @@ -14,10 +13,10 @@ export class EnumScope extends Scope { } /** The scope class for empty functions. */ -export class EmptyFunctionScope extends Scope { +export class EmptyFunctionScope extends TSESLintScope.Scope { constructor( scopeManager: ScopeManager, - upperScope: Scope, + upperScope: TSESLintScope.Scope, block: TSESTree.TSDeclareFunction | null, ) { super(scopeManager, 'empty-function', upperScope, block, false); diff --git a/packages/parser/typings/eslint-scope.d.ts b/packages/parser/typings/eslint-scope.d.ts deleted file mode 100644 index ec876c63716..00000000000 --- a/packages/parser/typings/eslint-scope.d.ts +++ /dev/null @@ -1,494 +0,0 @@ -// Type definitions for eslint-scope 4.0.0 -// Project: http://github.com/eslint/eslint-scope -// Definitions by: Armano - -//----------------------------------------------------------------------- -// TODO - figure out how to make ScopeManager exportable so that -// the module's type declaration files don't break -//----------------------------------------------------------------------- - -declare module 'eslint-scope/lib/options' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - export type PatternVisitorCallback = ( - pattern: TSESTree.Identifier, - info: { - rest: boolean; - topLevel: boolean; - assignments: TSESTree.AssignmentPattern[]; - }, - ) => void; - - export interface PatternVisitorOptions { - processRightHandNodes?: boolean; - } - - export abstract class Visitor { - visitChildren( - node?: T, - ): void; - visit(node?: T): void; - } -} - -declare module 'eslint-scope/lib/variable' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import Reference from 'eslint-scope/lib/reference'; - import { Definition } from 'eslint-scope/lib/definition'; - - export default class Variable { - name: string; - identifiers: TSESTree.Identifier[]; - references: Reference[]; - defs: Definition[]; - eslintUsed?: boolean; - } -} - -declare module 'eslint-scope/lib/definition' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - - export class Definition { - type: string; - name: TSESTree.BindingName; - node: TSESTree.Node; - parent?: TSESTree.Node | null; - index?: number | null; - kind?: string | null; - rest?: boolean; - - constructor( - type: string, - name: TSESTree.BindingName | TSESTree.PropertyName, - node: TSESTree.Node, - parent?: TSESTree.Node | null, - index?: number | null, - kind?: string | null, - ); - } - - export class ParameterDefinition extends Definition { - constructor( - name: TSESTree.Node, - node: TSESTree.Node, - index?: number | null, - rest?: boolean, - ); - } -} - -declare module 'eslint-scope/lib/pattern-visitor' { - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { - PatternVisitorCallback, - PatternVisitorOptions, - Visitor, - } from 'eslint-scope/lib/options'; - - export default class PatternVisitor extends Visitor { - protected options: any; - protected scopeManager: ScopeManager; - protected parent?: TSESTree.Node; - public rightHandNodes: TSESTree.Node[]; - - static isPattern(node: TSESTree.Node): boolean; - - constructor( - options: PatternVisitorOptions, - rootPattern: any, - callback: PatternVisitorCallback, - ); - - Identifier(pattern: TSESTree.Node): void; - Property(property: TSESTree.Node): void; - ArrayPattern(pattern: TSESTree.Node): void; - AssignmentPattern(pattern: TSESTree.Node): void; - RestElement(pattern: TSESTree.Node): void; - MemberExpression(node: TSESTree.Node): void; - SpreadElement(node: TSESTree.Node): void; - ArrayExpression(node: TSESTree.Node): void; - AssignmentExpression(node: TSESTree.Node): void; - CallExpression(node: TSESTree.Node): void; - } -} - -declare module 'eslint-scope/lib/referencer' { - import { Scope } from 'eslint-scope/lib/scope'; - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { - PatternVisitorCallback, - PatternVisitorOptions, - Visitor, - } from 'eslint-scope/lib/options'; - - export default class Referencer extends Visitor { - protected isInnerMethodDefinition: boolean; - protected options: any; - protected scopeManager: SM; - protected parent?: TSESTree.Node; - - constructor(options: any, scopeManager: SM); - - currentScope(): Scope; - close(node: TSESTree.Node): void; - pushInnerMethodDefinition(isInnerMethodDefinition: boolean): boolean; - popInnerMethodDefinition(isInnerMethodDefinition: boolean): void; - - referencingDefaultValue( - pattern: any, - assignments: any, - maybeImplicitGlobal: any, - init: boolean, - ): void; - visitPattern( - node: TSESTree.Node, - options: PatternVisitorOptions, - callback: PatternVisitorCallback, - ): void; - visitFunction(node: TSESTree.Node): void; - visitClass(node: TSESTree.Node): void; - visitProperty(node: TSESTree.Node): void; - visitForIn(node: TSESTree.Node): void; - visitVariableDeclaration( - variableTargetScope: any, - type: any, - node: TSESTree.Node, - index: any, - ): void; - - AssignmentExpression(node: TSESTree.Node): void; - CatchClause(node: TSESTree.Node): void; - Program(node: TSESTree.Node): void; - Identifier(node: TSESTree.Node): void; - UpdateExpression(node: TSESTree.Node): void; - MemberExpression(node: TSESTree.Node): void; - Property(node: TSESTree.Node): void; - MethodDefinition(node: TSESTree.Node): void; - BreakStatement(): void; - ContinueStatement(): void; - LabeledStatement(node: TSESTree.Node): void; - ForStatement(node: TSESTree.Node): void; - ClassExpression(node: TSESTree.Node): void; - ClassDeclaration(node: TSESTree.Node): void; - CallExpression(node: TSESTree.Node): void; - BlockStatement(node: TSESTree.Node): void; - ThisExpression(): void; - WithStatement(node: TSESTree.Node): void; - VariableDeclaration(node: TSESTree.Node): void; - SwitchStatement(node: TSESTree.Node): void; - FunctionDeclaration(node: TSESTree.Node): void; - FunctionExpression(node: TSESTree.Node): void; - ForOfStatement(node: TSESTree.Node): void; - ForInStatement(node: TSESTree.Node): void; - ArrowFunctionExpression(node: TSESTree.Node): void; - ImportDeclaration(node: TSESTree.Node): void; - visitExportDeclaration(node: TSESTree.Node): void; - ExportDeclaration(node: TSESTree.Node): void; - ExportNamedDeclaration(node: TSESTree.Node): void; - ExportSpecifier(node: TSESTree.Node): void; - MetaProperty(): void; - } -} - -declare module 'eslint-scope/lib/scope' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import Reference from 'eslint-scope/lib/reference'; - import Variable from 'eslint-scope/lib/variable'; - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import { Definition } from 'eslint-scope/lib/definition'; - - export type ScopeType = - | 'block' - | 'catch' - | 'class' - | 'for' - | 'function' - | 'function-expression-name' - | 'global' - | 'module' - | 'switch' - | 'with' - | 'TDZ' - | 'enum' - | 'empty-function'; - - export class Scope { - type: ScopeType; - isStrict: boolean; - upper: Scope | null; - childScopes: Scope[]; - variableScope: Scope; - block: TSESTree.Node; - variables: Variable[]; - set: Map; - references: Reference[]; - through: Reference[]; - thisFound?: boolean; - functionExpressionScope: boolean; - - constructor( - scopeManager: ScopeManager, - type: ScopeType, - upperScope: Scope | null, - block: TSESTree.Node | null, - isMethodDefinition: boolean, - ); - - __shouldStaticallyClose(scopeManager: ScopeManager): boolean; - __shouldStaticallyCloseForGlobal(ref: any): boolean; - __staticCloseRef(ref: any): void; - __dynamicCloseRef(ref: any): void; - __globalCloseRef(ref: any): void; - __close(scopeManager: ScopeManager): Scope; - __isValidResolution(ref: any, variable: any): boolean; - __resolve(ref: any): boolean; - __delegateToUpperScope(ref: any): void; - __addDeclaredVariablesOfNode(variable: any, node: TSESTree.Node): void; - __defineGeneric( - name: any, - set: any, - variables: any, - node: any, - def: Definition, - ): void; - - __define(node: TSESTree.Node, def: Definition): void; - - __referencing( - node: TSESTree.Node, - assign: number, - writeExpr: TSESTree.Node, - maybeImplicitGlobal: any, - partial: any, - init: any, - ): void; - - __detectEval(): void; - __detectThis(): void; - __isClosed(): boolean; - /** - * returns resolved {Reference} - * @method Scope#resolve - * @param {Espree.Identifier} ident - identifier to be resolved. - * @returns {Reference} reference - */ - resolve(ident: TSESTree.Node): Reference; - - /** - * returns this scope is static - * @method Scope#isStatic - * @returns {boolean} static - */ - isStatic(): boolean; - - /** - * returns this scope has materialized arguments - * @method Scope#isArgumentsMaterialized - * @returns {boolean} arguemnts materialized - */ - isArgumentsMaterialized(): boolean; - - /** - * returns this scope has materialized `this` reference - * @method Scope#isThisMaterialized - * @returns {boolean} this materialized - */ - isThisMaterialized(): boolean; - - isUsedName(name: any): boolean; - } - - export class GlobalScope extends Scope { - constructor(scopeManager: ScopeManager, block: TSESTree.Node | null); - } - - export class ModuleScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class FunctionExpressionNameScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class CatchScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class WithScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class BlockScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class SwitchScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class FunctionScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - isMethodDefinition: boolean, - ); - } - - export class ForScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } - - export class ClassScope extends Scope { - constructor( - scopeManager: ScopeManager, - upperScope: Scope, - block: TSESTree.Node | null, - ); - } -} - -declare module 'eslint-scope/lib/reference' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { Scope } from 'eslint-scope/lib/scope'; - import Variable from 'eslint-scope/lib/variable'; - - export default class Reference { - identifier: TSESTree.Identifier; - from: Scope; - resolved: Variable | null; - writeExpr: TSESTree.Node | null; - init: boolean; - - isWrite(): boolean; - isRead(): boolean; - isWriteOnly(): boolean; - isReadOnly(): boolean; - isReadWrite(): boolean; - - static READ: 0x1; - static WRITE: 0x2; - static RW: 0x3; - } -} - -declare module 'eslint-scope/lib/scope-manager' { - import { TSESTree } from '@typescript-eslint/experimental-utils'; - import { Scope } from 'eslint-scope/lib/scope'; - import Variable from 'eslint-scope/lib/variable'; - - export interface ScopeManagerOptions { - directive?: boolean; - optimistic?: boolean; - ignoreEval?: boolean; - nodejsScope?: boolean; - sourceType?: 'module' | 'script'; - impliedStrict?: boolean; - ecmaVersion?: number; - } - - export default class ScopeManager { - __options: ScopeManagerOptions; - __currentScope: Scope; - scopes: Scope[]; - globalScope: Scope; - - constructor(options: ScopeManagerOptions); - - __useDirective(): boolean; - __isOptimistic(): boolean; - __ignoreEval(): boolean; - __isNodejsScope(): boolean; - isModule(): boolean; - isImpliedStrict(): boolean; - isStrictModeSupported(): boolean; - - // Returns appropriate scope for this node. - __get(node: TSESTree.Node): Scope; - getDeclaredVariables(node: TSESTree.Node): Variable[]; - acquire(node: TSESTree.Node, inner?: boolean): Scope | null; - acquireAll(node: TSESTree.Node): Scope | null; - release(node: TSESTree.Node, inner?: boolean): Scope | null; - attach(): void; - detach(): void; - - __nestScope(scope: Scope): Scope; - __nestGlobalScope(node: TSESTree.Node): Scope; - __nestBlockScope(node: TSESTree.Node): Scope; - __nestFunctionScope( - node: TSESTree.Node, - isMethodDefinition: boolean, - ): Scope; - __nestForScope(node: TSESTree.Node): Scope; - __nestCatchScope(node: TSESTree.Node): Scope; - __nestWithScope(node: TSESTree.Node): Scope; - __nestClassScope(node: TSESTree.Node): Scope; - __nestSwitchScope(node: TSESTree.Node): Scope; - __nestModuleScope(node: TSESTree.Node): Scope; - __nestFunctionExpressionNameScope(node: TSESTree.Node): Scope; - - __isES6(): boolean; - } -} - -declare module 'eslint-scope' { - import ScopeManager from 'eslint-scope/lib/scope-manager'; - import Reference from 'eslint-scope/lib/reference'; - import Scope from 'eslint-scope/lib/scope'; - import Variable from 'eslint-scope/lib/variable'; - - interface AnalysisOptions { - optimistic?: boolean; - directive?: boolean; - ignoreEval?: boolean; - nodejsScope?: boolean; - impliedStrict?: boolean; - fallback?: string | ((node: {}) => string[]); - sourceType?: 'script' | 'module'; - ecmaVersion?: number; - } - function analyze(ast: {}, options?: AnalysisOptions): ScopeManager; - - const version: string; - - export { - AnalysisOptions, - version, - Reference, - Variable, - Scope, - ScopeManager, - analyze, - }; -} - -declare module 'eslint/lib/util/traverser'; diff --git a/packages/parser/typings/eslint.d.ts b/packages/parser/typings/eslint.d.ts new file mode 100644 index 00000000000..15a965cd48d --- /dev/null +++ b/packages/parser/typings/eslint.d.ts @@ -0,0 +1,14 @@ +declare module 'eslint/lib/util/traverser' { + import { TSESTree } from '@typescript-eslint/experimental-utils'; + const traverser: { + traverse( + node: TSESTree.Node, + options: { + enter?: (node: TSESTree.Node, parent: TSESTree.Node) => void; + leave?: (node: TSESTree.Node, parent: TSESTree.Node) => void; + visitorKeys?: Record; + }, + ): void; + }; + export = traverser; +} From 508bb41cfebf5aff96841b68b46c30e4b1a42367 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 15 May 2019 19:45:53 -0700 Subject: [PATCH 22/41] chore: tighter linting (#530) --- .eslintrc.js | 173 ++++++++++++++++++ .eslintrc.json | 73 -------- package.json | 11 +- packages/eslint-plugin-tslint/package.json | 3 +- packages/eslint-plugin/package.json | 4 +- .../src/rules/no-magic-numbers.ts | 2 +- .../src/rules/prefer-regexp-exec.ts | 4 +- .../eslint-plugin/tools/generate-configs.ts | 2 - packages/experimental-utils/package.json | 5 +- .../src/ts-eslint/CLIEngine.ts | 2 +- .../src/ts-eslint/Linter.ts | 2 +- .../experimental-utils/src/ts-eslint/Rule.ts | 2 +- .../src/ts-eslint/SourceCode.ts | 2 +- packages/parser/package.json | 4 +- packages/parser/typings/eslint.d.ts | 1 + packages/typescript-estree/package.json | 13 +- .../typescript-estree/src/ast-converter.ts | 2 +- packages/typescript-estree/src/parser.ts | 8 +- .../typescript-estree/src/semantic-errors.ts | 2 +- packages/typescript-estree/tests/lib/parse.ts | 2 +- .../utils/generate-package-json.js | 1 + yarn.lock | 123 ++++++++++++- 22 files changed, 333 insertions(+), 108 deletions(-) create mode 100644 .eslintrc.js delete mode 100644 .eslintrc.json diff --git a/.eslintrc.js b/.eslintrc.js new file mode 100644 index 00000000000..84ce4321e0d --- /dev/null +++ b/.eslintrc.js @@ -0,0 +1,173 @@ +module.exports = { + root: true, + plugins: [ + 'eslint-plugin', + '@typescript-eslint', + 'jest', + 'import', + 'eslint-comments', + ], + env: { + es6: true, + node: true, + }, + extends: [ + 'eslint:recommended', + 'plugin:@typescript-eslint/eslint-recommended', + 'plugin:@typescript-eslint/recommended', + ], + rules: { + // + // eslint base + // + + 'comma-dangle': ['error', 'always-multiline'], + curly: ['error', 'all'], + 'no-mixed-operators': 'error', + 'no-console': 'error', + 'no-process-exit': 'error', + + // + // our plugin :D + // + + '@typescript-eslint/indent': 'off', + '@typescript-eslint/no-explicit-any': 'off', + '@typescript-eslint/no-non-null-assertion': 'off', + '@typescript-eslint/explicit-function-return-type': 'off', + '@typescript-eslint/explicit-member-accessibility': 'off', + '@typescript-eslint/no-var-requires': 'off', + '@typescript-eslint/no-use-before-define': 'off', + '@typescript-eslint/no-object-literal-type-assertion': 'off', + '@typescript-eslint/no-parameter-properties': 'off', + + // + // eslint-plugin-import + // + + // disallow non-import statements appearing before import statements + 'import/first': 'error', + // Require a newline after the last import/require in a group + 'import/newline-after-import': 'error', + // Forbid import of modules using absolute paths + 'import/no-absolute-path': 'error', + // disallow AMD require/define + 'import/no-amd': 'error', + // forbid default exports + 'import/no-default-export': 'error', + // Forbid the use of extraneous packages + 'import/no-extraneous-dependencies': [ + 'error', + { + devDependencies: true, + peerDependencies: true, + optionalDependencies: false, + }, + ], + // Forbid mutable exports + 'import/no-mutable-exports': 'error', + // Prevent importing the default as if it were named + 'import/no-named-default': 'error', + // Prohibit named exports // we want everything to be a named export + 'import/no-named-export': 'off', + // Forbid a module from importing itself + 'import/no-self-import': 'error', + // Require modules with a single export to use a default export // we want everything to be named + 'import/prefer-default-export': 'off', + + // + // eslint-plugin-eslint-comment + // + + // require a eslint-enable comment for every eslint-disable comment + 'eslint-comments/disable-enable-pair': [ + 'error', + { + allowWholeFile: true, + }, + ], + // disallow a eslint-enable comment for multiple eslint-disable comments + 'eslint-comments/no-aggregating-enable': 'error', + // disallow duplicate eslint-disable comments + 'eslint-comments/no-duplicate-disable': 'error', + // disallow eslint-disable comments without rule names + 'eslint-comments/no-unlimited-disable': 'error', + // disallow unused eslint-disable comments + 'eslint-comments/no-unused-disable': 'error', + // disallow unused eslint-enable comments + 'eslint-comments/no-unused-enable': 'error', + // disallow ESLint directive-comments + 'eslint-comments/no-use': [ + 'error', + { + allow: [ + 'eslint-disable', + 'eslint-disable-line', + 'eslint-disable-next-line', + 'eslint-enable', + ], + }, + ], + }, + parserOptions: { + sourceType: 'module', + ecmaFeatures: { + jsx: false, + }, + project: './tsconfig.base.json', + }, + overrides: [ + { + files: [ + 'packages/eslint-plugin-tslint/tests/**/*.ts', + 'packages/eslint-plugin/tests/**/*.test.ts', + 'packages/parser/tests/**/*.ts', + 'packages/typescript-estree/tests/**/*.ts', + ], + env: { + 'jest/globals': true, + }, + rules: { + 'jest/no-disabled-tests': 'warn', + 'jest/no-focused-tests': 'error', + 'jest/no-alias-methods': 'error', + 'jest/no-identical-title': 'error', + 'jest/no-jasmine-globals': 'error', + 'jest/no-jest-import': 'error', + 'jest/no-test-prefixes': 'error', + 'jest/no-test-callback': 'error', + 'jest/no-test-return-statement': 'error', + 'jest/prefer-to-have-length': 'warn', + 'jest/prefer-spy-on': 'error', + 'jest/valid-expect': 'error', + }, + }, + { + files: [ + 'packages/eslint-plugin/tests/**/*.test.ts', + 'packages/eslint-plugin-tslint/tests/**/*.spec.ts', + ], + rules: { + 'eslint-plugin/no-identical-tests': 'error', + }, + }, + { + files: [ + 'packages/eslint-plugin/src/rules/**/*.ts', + 'packages/eslint-plugin/src/configs/**/*.ts', + 'packages/eslint-plugin-tslint/src/rules/**/*.ts', + ], + rules: { + // specifically for rules - default exports makes the tooling easier + 'import/no-default-export': 'off', + }, + }, + { + files: ['**/tools/**/*.ts', '**/tests/**/*.ts'], + rules: { + // allow console logs in tools and tests + 'no-console': 'off', + }, + }, + ], +}; diff --git a/.eslintrc.json b/.eslintrc.json deleted file mode 100644 index 649b9ec2502..00000000000 --- a/.eslintrc.json +++ /dev/null @@ -1,73 +0,0 @@ -{ - "root": true, - "plugins": ["eslint-plugin", "@typescript-eslint", "jest"], - "env": { - "es6": true, - "node": true - }, - "extends": [ - "eslint:recommended", - "plugin:@typescript-eslint/eslint-recommended", - "plugin:@typescript-eslint/recommended" - ], - "rules": { - "comma-dangle": ["error", "always-multiline"], - "curly": ["error", "all"], - "no-mixed-operators": "error", - "no-console": "off", - "no-dupe-class-members": "off", - "no-undef": "off", - "@typescript-eslint/indent": "off", - "@typescript-eslint/no-explicit-any": "off", - "@typescript-eslint/no-non-null-assertion": "off", - "@typescript-eslint/explicit-function-return-type": "off", - "@typescript-eslint/explicit-member-accessibility": "off", - "@typescript-eslint/no-var-requires": "off", - "@typescript-eslint/no-use-before-define": "off", - "@typescript-eslint/no-object-literal-type-assertion": "off", - "@typescript-eslint/no-parameter-properties": "off" - }, - "parserOptions": { - "sourceType": "module", - "ecmaFeatures": { - "jsx": false - }, - "project": "./tsconfig.base.json" - }, - "overrides": [ - { - "files": [ - "packages/eslint-plugin-tslint/tests/**/*.ts", - "packages/eslint-plugin/tests/**/*.test.ts", - "packages/parser/tests/**/*.ts", - "packages/typescript-estree/tests/**/*.ts" - ], - "env": { - "jest/globals": true - }, - "rules": { - "jest/no-disabled-tests": "warn", - "jest/no-focused-tests": "error", - "jest/no-alias-methods": "error", - "jest/no-identical-title": "error", - "jest/no-jasmine-globals": "error", - "jest/no-jest-import": "error", - "jest/no-test-prefixes": "error", - "jest/no-test-callback": "error", - "jest/no-test-return-statement": "error", - "jest/prefer-to-have-length": "warn", - "jest/prefer-spy-on": "error", - "jest/valid-expect": "error" - } - }, - { - "files": [ - "packages/eslint-plugin/test/**/*.ts", - "packages/eslint-plugin-tslint/tests/**/*.spec.ts" - ], - "rules": { - "eslint-plugin/no-identical-tests": "error" - } - } - ] -} diff --git a/package.json b/package.json index 8d9760c2c25..b05e48b7fec 100644 --- a/package.json +++ b/package.json @@ -47,23 +47,17 @@ "node": ">=6.14.0" }, "devDependencies": { - "@babel/code-frame": "7.0.0", - "@babel/parser": "7.3.2", "@commitlint/cli": "^7.1.2", "@commitlint/config-conventional": "^7.1.2", "@commitlint/travis-cli": "^7.1.2", - "@types/babel-code-frame": "^6.20.1", - "@types/glob": "^7.1.1", "@types/jest": "^24.0.6", - "@types/lodash.isplainobject": "^4.0.4", - "@types/lodash.unescape": "^4.0.4", "@types/node": "^10.12.2", - "@types/semver": "^5.5.0", "all-contributors-cli": "^6.0.0", - "babel-code-frame": "^6.26.0", "cz-conventional-changelog": "2.1.0", "eslint": "^5.12.1", + "eslint-plugin-eslint-comments": "^3.1.1", "eslint-plugin-eslint-plugin": "^2.0.1", + "eslint-plugin-import": "^2.17.2", "eslint-plugin-jest": "^22.2.2", "glob": "7.1.2", "husky": "^1.3.1", @@ -71,7 +65,6 @@ "jest": "24.3.0", "lerna": "^3.10.5", "lint-staged": "8.1.0", - "lodash.isplainobject": "4.0.6", "prettier": "^1.17.0", "rimraf": "^2.6.3", "ts-jest": "^24.0.0", diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index fba222f6dbf..9139313a3c3 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -32,7 +32,8 @@ }, "peerDependencies": { "eslint": "^5.0.0", - "tslint": "^5.0.0" + "tslint": "^5.0.0", + "typescript": "*" }, "devDependencies": { "@types/json-schema": "^7.0.3", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a52aacc2352..78ab2e03b26 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -44,7 +44,9 @@ "tsutils": "^3.7.0" }, "devDependencies": { - "eslint-docs": "^0.2.6" + "@typescript-eslint/parser": "1.9.0", + "eslint-docs": "^0.2.6", + "typescript": "*" }, "peerDependencies": { "@typescript-eslint/parser": "1.9.0", diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts index 49689213992..75812ffb4d2 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -8,8 +8,8 @@ import { AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-magic-numbers'; +import { JSONSchema4 } from 'json-schema'; // eslint-disable-line import/no-extraneous-dependencies import * as util from '../util'; -import { JSONSchema4 } from 'json-schema'; type Options = util.InferOptionsTypeFromRule; type MessageIds = util.InferMessageIdsTypeFromRule; diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index ea4e6742d05..b9f18fe5156 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -1,6 +1,6 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; -import { createRule, getParserServices, getTypeName } from '../util'; +import { TSESTree } from '@typescript-eslint/experimental-utils'; import { getStaticValue } from 'eslint-utils'; +import { createRule, getParserServices, getTypeName } from '../util'; export default createRule({ name: 'prefer-regexp-exec', diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index 9809adc05c7..f630428ca2a 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -1,5 +1,3 @@ -/* eslint-disable no-console */ - import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import path from 'path'; diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 9d51a32c4fd..a177f3cc3bc 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -32,11 +32,14 @@ "typecheck": "tsc --noEmit" }, "dependencies": { + "@types/json-schema": "^7.0.3", "@typescript-eslint/typescript-estree": "1.9.0", "eslint-scope": "^4.0.0" }, "peerDependencies": { - "eslint": "*", + "eslint": "*" + }, + "devDependencies": { "typescript": "*" } } diff --git a/packages/experimental-utils/src/ts-eslint/CLIEngine.ts b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts index 0a64a3d6734..76b3983d370 100644 --- a/packages/experimental-utils/src/ts-eslint/CLIEngine.ts +++ b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ +/* eslint-disable @typescript-eslint/no-namespace */ import { CLIEngine as ESLintCLIEngine } from 'eslint'; import { Linter } from './Linter'; diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts index dde85a07f2b..854b7d46155 100644 --- a/packages/experimental-utils/src/ts-eslint/Linter.ts +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ +/* eslint-disable @typescript-eslint/no-namespace */ import { TSESTree, ParserServices } from '@typescript-eslint/typescript-estree'; import { Linter as ESLintLinter } from 'eslint'; diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index 388f64e99fc..ebd376caa80 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -1,5 +1,5 @@ import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; -import { JSONSchema4 } from 'json-schema'; +import { JSONSchema4 } from 'json-schema'; // eslint-disable-line import/no-extraneous-dependencies import { AST } from './AST'; import { Linter } from './Linter'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts index 2fb2e0b3cab..ac833116606 100644 --- a/packages/experimental-utils/src/ts-eslint/SourceCode.ts +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ +/* eslint-disable @typescript-eslint/no-namespace */ import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; import { SourceCode as ESLintSourceCode } from 'eslint'; diff --git a/packages/parser/package.json b/packages/parser/package.json index d33fa890f50..1febddba372 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -43,6 +43,8 @@ "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@typescript-eslint/shared-fixtures": "1.9.0" + "@types/glob": "^7.1.1", + "@typescript-eslint/shared-fixtures": "1.9.0", + "glob": "^7.1.4" } } diff --git a/packages/parser/typings/eslint.d.ts b/packages/parser/typings/eslint.d.ts index 15a965cd48d..c90febb3c47 100644 --- a/packages/parser/typings/eslint.d.ts +++ b/packages/parser/typings/eslint.d.ts @@ -1,5 +1,6 @@ declare module 'eslint/lib/util/traverser' { import { TSESTree } from '@typescript-eslint/experimental-utils'; + const traverser: { traverse( node: TSESTree.Node, diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 65b6b41440f..9c676a297f1 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -41,7 +41,18 @@ "semver": "5.5.0" }, "devDependencies": { + "@babel/code-frame": "7.0.0", + "@babel/parser": "7.3.2", "@babel/types": "^7.3.2", - "@typescript-eslint/shared-fixtures": "1.9.0" + "@types/glob": "^7.1.1", + "@types/babel-code-frame": "^6.20.1", + "@types/lodash.unescape": "^4.0.4", + "@types/lodash.isplainobject": "^4.0.4", + "@types/semver": "^5.5.0", + "@typescript-eslint/shared-fixtures": "1.9.0", + "babel-code-frame": "^6.26.0", + "lodash.isplainobject": "4.0.6", + "typescript": "*", + "glob": "^7.1.4" } } diff --git a/packages/typescript-estree/src/ast-converter.ts b/packages/typescript-estree/src/ast-converter.ts index 25d291dee4c..8488fa6f7d8 100644 --- a/packages/typescript-estree/src/ast-converter.ts +++ b/packages/typescript-estree/src/ast-converter.ts @@ -4,7 +4,7 @@ import { convertComments } from './convert-comments'; import { convertTokens } from './node-utils'; import { Extra } from './parser-options'; -export default function astConverter( +export function astConverter( ast: SourceFile, extra: Extra, shouldPreserveNodeMaps: boolean, diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 1a963859d96..389c819d2a0 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,6 +1,6 @@ import semver from 'semver'; import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports -import convert from './ast-converter'; +import { astConverter } from './ast-converter'; import { convertError } from './convert'; import { firstDefined } from './node-utils'; import { Extra, TSESTreeOptions, ParserServices } from './parser-options'; @@ -50,7 +50,7 @@ function resetExtra(): void { strict: false, jsx: false, useJSXTextNode: false, - log: console.log, + log: console.log, // eslint-disable-line no-console projects: [], errorOnUnknownASTType: false, errorOnTypeScriptSyntacticAndSemanticIssues: false, @@ -339,7 +339,7 @@ export function parse( /** * Convert the TypeScript AST to an ESTree-compatible one */ - const { estree } = convert(ast, extra, false); + const { estree } = astConverter(ast, extra, false); return estree as AST; } @@ -397,7 +397,7 @@ export function parseAndGenerateServices< * Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve * mappings between converted and original AST nodes */ - const { estree, astMaps } = convert(ast, extra, shouldPreserveNodeMaps); + const { estree, astMaps } = astConverter(ast, extra, shouldPreserveNodeMaps); /** * Even if TypeScript parsed the source code ok, and we had no problems converting the AST, * there may be other syntactic or semantic issues in the code that we can optionally report on. diff --git a/packages/typescript-estree/src/semantic-errors.ts b/packages/typescript-estree/src/semantic-errors.ts index f31eb634068..c580697a185 100644 --- a/packages/typescript-estree/src/semantic-errors.ts +++ b/packages/typescript-estree/src/semantic-errors.ts @@ -45,7 +45,7 @@ export function getFirstSemanticOrSyntacticError( * and log a a warning. */ /* istanbul ignore next */ - console.warn(`Warning From TSC: "${e.message}`); + console.warn(`Warning From TSC: "${e.message}`); // eslint-disable-line no-console /* istanbul ignore next */ return undefined; } diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 3c34f705556..3af2a91d327 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -58,7 +58,7 @@ describe('parse()', () => { describe('loggerFn should be propagated to ast-converter', () => { it('output tokens, comments, locs, and ranges when called with those options', () => { - const spy = jest.spyOn(astConverter, 'default'); + const spy = jest.spyOn(astConverter, 'astConverter'); const loggerFn = jest.fn(() => true); diff --git a/tests/integration/utils/generate-package-json.js b/tests/integration/utils/generate-package-json.js index bf173d5e35c..1f6af28df63 100644 --- a/tests/integration/utils/generate-package-json.js +++ b/tests/integration/utils/generate-package-json.js @@ -1,4 +1,5 @@ const fs = require('fs'); +// eslint-disable-next-line import/no-absolute-path const rootPackageJSON = require('/usr/root-package.json'); /** diff --git a/yarn.lock b/yarn.lock index 4f4a0d81f56..bba6723622b 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1485,6 +1485,14 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= +array-includes@^3.0.3: + version "3.0.3" + resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" + integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= + dependencies: + define-properties "^1.1.2" + es-abstract "^1.7.0" + array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -2076,6 +2084,11 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= +contains-path@^0.1.0: + version "0.1.0" + resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" + integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= + conventional-changelog-angular@^1.3.3: version "1.6.6" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz#b27f2b315c16d0a1f23eb181309d0e6a4698ea0f" @@ -2343,7 +2356,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^2.2.0, debug@^2.3.3: +debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2518,6 +2531,14 @@ dir-glob@2.0.0: arrify "^1.0.1" path-type "^3.0.0" +doctrine@1.5.0: + version "1.5.0" + resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" + integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= + dependencies: + esutils "^2.0.2" + isarray "^1.0.0" + doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -2605,7 +2626,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.5.1: +es-abstract@^1.5.1, es-abstract@^1.7.0: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== @@ -2667,11 +2688,52 @@ eslint-docs@^0.2.6: ora "^3.0.0" read-pkg-up "^4.0.0" +eslint-import-resolver-node@^0.3.2: + version "0.3.2" + resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" + integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== + dependencies: + debug "^2.6.9" + resolve "^1.5.0" + +eslint-module-utils@^2.4.0: + version "2.4.0" + resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a" + integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw== + dependencies: + debug "^2.6.8" + pkg-dir "^2.0.0" + +eslint-plugin-eslint-comments@^3.1.1: + version "3.1.1" + resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.1.tgz#32ff0afba8a48e17073817e6d03fbc5622f735b7" + integrity sha512-GZDKhOFqJLKlaABX+kdoLskcTINMrVOWxGca54KcFb1QCPd0CLmqgAMRxkkUfGSmN+5NJUMGh7NGccIMcWPSfQ== + dependencies: + escape-string-regexp "^1.0.5" + ignore "^5.0.5" + eslint-plugin-eslint-plugin@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.0.1.tgz#d275434969dbde3da1d4cb7a121dc8d88457c786" integrity sha512-kJ5TZsRJH/xYstG07v3YeOy/W5SDAEzV+bvvoL0aiG1HtqDmg4mJvNPnn/JngANMmsx8oXlJrIcBTCpJzm+9kg== +eslint-plugin-import@^2.17.2: + version "2.17.2" + resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz#d227d5c6dc67eca71eb590d2bb62fb38d86e9fcb" + integrity sha512-m+cSVxM7oLsIpmwNn2WXTJoReOF9f/CtLMo7qOVmKd1KntBy0hEcuNZ3erTmWjx+DxRO0Zcrm5KwAvI9wHcV5g== + dependencies: + array-includes "^3.0.3" + contains-path "^0.1.0" + debug "^2.6.9" + doctrine "1.5.0" + eslint-import-resolver-node "^0.3.2" + eslint-module-utils "^2.4.0" + has "^1.0.3" + lodash "^4.17.11" + minimatch "^3.0.4" + read-pkg-up "^2.0.0" + resolve "^1.10.0" + eslint-plugin-jest@^22.2.2: version "22.5.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.5.1.tgz#a31dfe9f9513c6af7c17ece4c65535a1370f060b" @@ -3310,6 +3372,18 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.4: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -3534,6 +3608,11 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== +ignore@^5.0.5: + version "5.1.1" + resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.1.tgz#2fc6b8f518aff48fef65a7f348ed85632448e4a5" + integrity sha512-DWjnQIFLenVrwyRCKZT+7a7/U4Cqgar4WG8V++K3hw+lrW1hc/SIwdiGmtxKCVACmHULTuGeBbHJmbwW7/sAvA== + import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -3911,7 +3990,7 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -isarray@1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -4691,6 +4770,16 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" +load-json-file@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" + integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= + dependencies: + graceful-fs "^4.1.2" + parse-json "^2.2.0" + pify "^2.0.0" + strip-bom "^3.0.0" + load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -5805,6 +5894,13 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" +path-type@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" + integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= + dependencies: + pify "^2.0.0" + path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -6078,6 +6174,14 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" +read-pkg-up@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" + integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= + dependencies: + find-up "^2.0.0" + read-pkg "^2.0.0" + read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -6103,6 +6207,15 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" +read-pkg@^2.0.0: + version "2.0.0" + resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" + integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= + dependencies: + load-json-file "^2.0.0" + normalize-package-data "^2.3.2" + path-type "^2.0.0" + read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -6329,7 +6442,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: +resolve@1.x, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.5.0: version "1.10.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== @@ -7190,7 +7303,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -"typescript@>=3.2.1 <3.5.0": +typescript@*, "typescript@>=3.2.1 <3.5.0": version "3.4.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== From c480eabb60a5876f565d00f71240b6a10726f309 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 15 May 2019 20:17:32 -0700 Subject: [PATCH 23/41] chore: revert 508bb41 (#534) This reverts commit 508bb41cfebf5aff96841b68b46c30e4b1a42367. --- .eslintrc.js | 173 ------------------ .eslintrc.json | 73 ++++++++ package.json | 11 +- packages/eslint-plugin-tslint/package.json | 3 +- packages/eslint-plugin/package.json | 4 +- .../src/rules/no-magic-numbers.ts | 2 +- .../src/rules/prefer-regexp-exec.ts | 4 +- .../eslint-plugin/tools/generate-configs.ts | 2 + packages/experimental-utils/package.json | 5 +- .../src/ts-eslint/CLIEngine.ts | 2 +- .../src/ts-eslint/Linter.ts | 2 +- .../experimental-utils/src/ts-eslint/Rule.ts | 2 +- .../src/ts-eslint/SourceCode.ts | 2 +- packages/parser/package.json | 4 +- packages/parser/typings/eslint.d.ts | 1 - packages/typescript-estree/package.json | 13 +- .../typescript-estree/src/ast-converter.ts | 2 +- packages/typescript-estree/src/parser.ts | 8 +- .../typescript-estree/src/semantic-errors.ts | 2 +- packages/typescript-estree/tests/lib/parse.ts | 2 +- .../utils/generate-package-json.js | 1 - yarn.lock | 123 +------------ 22 files changed, 108 insertions(+), 333 deletions(-) delete mode 100644 .eslintrc.js create mode 100644 .eslintrc.json diff --git a/.eslintrc.js b/.eslintrc.js deleted file mode 100644 index 84ce4321e0d..00000000000 --- a/.eslintrc.js +++ /dev/null @@ -1,173 +0,0 @@ -module.exports = { - root: true, - plugins: [ - 'eslint-plugin', - '@typescript-eslint', - 'jest', - 'import', - 'eslint-comments', - ], - env: { - es6: true, - node: true, - }, - extends: [ - 'eslint:recommended', - 'plugin:@typescript-eslint/eslint-recommended', - 'plugin:@typescript-eslint/recommended', - ], - rules: { - // - // eslint base - // - - 'comma-dangle': ['error', 'always-multiline'], - curly: ['error', 'all'], - 'no-mixed-operators': 'error', - 'no-console': 'error', - 'no-process-exit': 'error', - - // - // our plugin :D - // - - '@typescript-eslint/indent': 'off', - '@typescript-eslint/no-explicit-any': 'off', - '@typescript-eslint/no-non-null-assertion': 'off', - '@typescript-eslint/explicit-function-return-type': 'off', - '@typescript-eslint/explicit-member-accessibility': 'off', - '@typescript-eslint/no-var-requires': 'off', - '@typescript-eslint/no-use-before-define': 'off', - '@typescript-eslint/no-object-literal-type-assertion': 'off', - '@typescript-eslint/no-parameter-properties': 'off', - - // - // eslint-plugin-import - // - - // disallow non-import statements appearing before import statements - 'import/first': 'error', - // Require a newline after the last import/require in a group - 'import/newline-after-import': 'error', - // Forbid import of modules using absolute paths - 'import/no-absolute-path': 'error', - // disallow AMD require/define - 'import/no-amd': 'error', - // forbid default exports - 'import/no-default-export': 'error', - // Forbid the use of extraneous packages - 'import/no-extraneous-dependencies': [ - 'error', - { - devDependencies: true, - peerDependencies: true, - optionalDependencies: false, - }, - ], - // Forbid mutable exports - 'import/no-mutable-exports': 'error', - // Prevent importing the default as if it were named - 'import/no-named-default': 'error', - // Prohibit named exports // we want everything to be a named export - 'import/no-named-export': 'off', - // Forbid a module from importing itself - 'import/no-self-import': 'error', - // Require modules with a single export to use a default export // we want everything to be named - 'import/prefer-default-export': 'off', - - // - // eslint-plugin-eslint-comment - // - - // require a eslint-enable comment for every eslint-disable comment - 'eslint-comments/disable-enable-pair': [ - 'error', - { - allowWholeFile: true, - }, - ], - // disallow a eslint-enable comment for multiple eslint-disable comments - 'eslint-comments/no-aggregating-enable': 'error', - // disallow duplicate eslint-disable comments - 'eslint-comments/no-duplicate-disable': 'error', - // disallow eslint-disable comments without rule names - 'eslint-comments/no-unlimited-disable': 'error', - // disallow unused eslint-disable comments - 'eslint-comments/no-unused-disable': 'error', - // disallow unused eslint-enable comments - 'eslint-comments/no-unused-enable': 'error', - // disallow ESLint directive-comments - 'eslint-comments/no-use': [ - 'error', - { - allow: [ - 'eslint-disable', - 'eslint-disable-line', - 'eslint-disable-next-line', - 'eslint-enable', - ], - }, - ], - }, - parserOptions: { - sourceType: 'module', - ecmaFeatures: { - jsx: false, - }, - project: './tsconfig.base.json', - }, - overrides: [ - { - files: [ - 'packages/eslint-plugin-tslint/tests/**/*.ts', - 'packages/eslint-plugin/tests/**/*.test.ts', - 'packages/parser/tests/**/*.ts', - 'packages/typescript-estree/tests/**/*.ts', - ], - env: { - 'jest/globals': true, - }, - rules: { - 'jest/no-disabled-tests': 'warn', - 'jest/no-focused-tests': 'error', - 'jest/no-alias-methods': 'error', - 'jest/no-identical-title': 'error', - 'jest/no-jasmine-globals': 'error', - 'jest/no-jest-import': 'error', - 'jest/no-test-prefixes': 'error', - 'jest/no-test-callback': 'error', - 'jest/no-test-return-statement': 'error', - 'jest/prefer-to-have-length': 'warn', - 'jest/prefer-spy-on': 'error', - 'jest/valid-expect': 'error', - }, - }, - { - files: [ - 'packages/eslint-plugin/tests/**/*.test.ts', - 'packages/eslint-plugin-tslint/tests/**/*.spec.ts', - ], - rules: { - 'eslint-plugin/no-identical-tests': 'error', - }, - }, - { - files: [ - 'packages/eslint-plugin/src/rules/**/*.ts', - 'packages/eslint-plugin/src/configs/**/*.ts', - 'packages/eslint-plugin-tslint/src/rules/**/*.ts', - ], - rules: { - // specifically for rules - default exports makes the tooling easier - 'import/no-default-export': 'off', - }, - }, - { - files: ['**/tools/**/*.ts', '**/tests/**/*.ts'], - rules: { - // allow console logs in tools and tests - 'no-console': 'off', - }, - }, - ], -}; diff --git a/.eslintrc.json b/.eslintrc.json new file mode 100644 index 00000000000..649b9ec2502 --- /dev/null +++ b/.eslintrc.json @@ -0,0 +1,73 @@ +{ + "root": true, + "plugins": ["eslint-plugin", "@typescript-eslint", "jest"], + "env": { + "es6": true, + "node": true + }, + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ], + "rules": { + "comma-dangle": ["error", "always-multiline"], + "curly": ["error", "all"], + "no-mixed-operators": "error", + "no-console": "off", + "no-dupe-class-members": "off", + "no-undef": "off", + "@typescript-eslint/indent": "off", + "@typescript-eslint/no-explicit-any": "off", + "@typescript-eslint/no-non-null-assertion": "off", + "@typescript-eslint/explicit-function-return-type": "off", + "@typescript-eslint/explicit-member-accessibility": "off", + "@typescript-eslint/no-var-requires": "off", + "@typescript-eslint/no-use-before-define": "off", + "@typescript-eslint/no-object-literal-type-assertion": "off", + "@typescript-eslint/no-parameter-properties": "off" + }, + "parserOptions": { + "sourceType": "module", + "ecmaFeatures": { + "jsx": false + }, + "project": "./tsconfig.base.json" + }, + "overrides": [ + { + "files": [ + "packages/eslint-plugin-tslint/tests/**/*.ts", + "packages/eslint-plugin/tests/**/*.test.ts", + "packages/parser/tests/**/*.ts", + "packages/typescript-estree/tests/**/*.ts" + ], + "env": { + "jest/globals": true + }, + "rules": { + "jest/no-disabled-tests": "warn", + "jest/no-focused-tests": "error", + "jest/no-alias-methods": "error", + "jest/no-identical-title": "error", + "jest/no-jasmine-globals": "error", + "jest/no-jest-import": "error", + "jest/no-test-prefixes": "error", + "jest/no-test-callback": "error", + "jest/no-test-return-statement": "error", + "jest/prefer-to-have-length": "warn", + "jest/prefer-spy-on": "error", + "jest/valid-expect": "error" + } + }, + { + "files": [ + "packages/eslint-plugin/test/**/*.ts", + "packages/eslint-plugin-tslint/tests/**/*.spec.ts" + ], + "rules": { + "eslint-plugin/no-identical-tests": "error" + } + } + ] +} diff --git a/package.json b/package.json index b05e48b7fec..8d9760c2c25 100644 --- a/package.json +++ b/package.json @@ -47,17 +47,23 @@ "node": ">=6.14.0" }, "devDependencies": { + "@babel/code-frame": "7.0.0", + "@babel/parser": "7.3.2", "@commitlint/cli": "^7.1.2", "@commitlint/config-conventional": "^7.1.2", "@commitlint/travis-cli": "^7.1.2", + "@types/babel-code-frame": "^6.20.1", + "@types/glob": "^7.1.1", "@types/jest": "^24.0.6", + "@types/lodash.isplainobject": "^4.0.4", + "@types/lodash.unescape": "^4.0.4", "@types/node": "^10.12.2", + "@types/semver": "^5.5.0", "all-contributors-cli": "^6.0.0", + "babel-code-frame": "^6.26.0", "cz-conventional-changelog": "2.1.0", "eslint": "^5.12.1", - "eslint-plugin-eslint-comments": "^3.1.1", "eslint-plugin-eslint-plugin": "^2.0.1", - "eslint-plugin-import": "^2.17.2", "eslint-plugin-jest": "^22.2.2", "glob": "7.1.2", "husky": "^1.3.1", @@ -65,6 +71,7 @@ "jest": "24.3.0", "lerna": "^3.10.5", "lint-staged": "8.1.0", + "lodash.isplainobject": "4.0.6", "prettier": "^1.17.0", "rimraf": "^2.6.3", "ts-jest": "^24.0.0", diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index 9139313a3c3..fba222f6dbf 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -32,8 +32,7 @@ }, "peerDependencies": { "eslint": "^5.0.0", - "tslint": "^5.0.0", - "typescript": "*" + "tslint": "^5.0.0" }, "devDependencies": { "@types/json-schema": "^7.0.3", diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 78ab2e03b26..a52aacc2352 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -44,9 +44,7 @@ "tsutils": "^3.7.0" }, "devDependencies": { - "@typescript-eslint/parser": "1.9.0", - "eslint-docs": "^0.2.6", - "typescript": "*" + "eslint-docs": "^0.2.6" }, "peerDependencies": { "@typescript-eslint/parser": "1.9.0", diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts index 75812ffb4d2..49689213992 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -8,8 +8,8 @@ import { AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; import baseRule from 'eslint/lib/rules/no-magic-numbers'; -import { JSONSchema4 } from 'json-schema'; // eslint-disable-line import/no-extraneous-dependencies import * as util from '../util'; +import { JSONSchema4 } from 'json-schema'; type Options = util.InferOptionsTypeFromRule; type MessageIds = util.InferMessageIdsTypeFromRule; diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index b9f18fe5156..ea4e6742d05 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -1,6 +1,6 @@ -import { TSESTree } from '@typescript-eslint/experimental-utils'; -import { getStaticValue } from 'eslint-utils'; +import { TSESTree } from '@typescript-eslint/typescript-estree'; import { createRule, getParserServices, getTypeName } from '../util'; +import { getStaticValue } from 'eslint-utils'; export default createRule({ name: 'prefer-regexp-exec', diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index f630428ca2a..9809adc05c7 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -1,3 +1,5 @@ +/* eslint-disable no-console */ + import { TSESLint } from '@typescript-eslint/experimental-utils'; import fs from 'fs'; import path from 'path'; diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index a177f3cc3bc..9d51a32c4fd 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -32,14 +32,11 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@types/json-schema": "^7.0.3", "@typescript-eslint/typescript-estree": "1.9.0", "eslint-scope": "^4.0.0" }, "peerDependencies": { - "eslint": "*" - }, - "devDependencies": { + "eslint": "*", "typescript": "*" } } diff --git a/packages/experimental-utils/src/ts-eslint/CLIEngine.ts b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts index 76b3983d370..0a64a3d6734 100644 --- a/packages/experimental-utils/src/ts-eslint/CLIEngine.ts +++ b/packages/experimental-utils/src/ts-eslint/CLIEngine.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ import { CLIEngine as ESLintCLIEngine } from 'eslint'; import { Linter } from './Linter'; diff --git a/packages/experimental-utils/src/ts-eslint/Linter.ts b/packages/experimental-utils/src/ts-eslint/Linter.ts index 854b7d46155..dde85a07f2b 100644 --- a/packages/experimental-utils/src/ts-eslint/Linter.ts +++ b/packages/experimental-utils/src/ts-eslint/Linter.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ import { TSESTree, ParserServices } from '@typescript-eslint/typescript-estree'; import { Linter as ESLintLinter } from 'eslint'; diff --git a/packages/experimental-utils/src/ts-eslint/Rule.ts b/packages/experimental-utils/src/ts-eslint/Rule.ts index ebd376caa80..388f64e99fc 100644 --- a/packages/experimental-utils/src/ts-eslint/Rule.ts +++ b/packages/experimental-utils/src/ts-eslint/Rule.ts @@ -1,5 +1,5 @@ import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; -import { JSONSchema4 } from 'json-schema'; // eslint-disable-line import/no-extraneous-dependencies +import { JSONSchema4 } from 'json-schema'; import { AST } from './AST'; import { Linter } from './Linter'; import { Scope } from './Scope'; diff --git a/packages/experimental-utils/src/ts-eslint/SourceCode.ts b/packages/experimental-utils/src/ts-eslint/SourceCode.ts index ac833116606..2fb2e0b3cab 100644 --- a/packages/experimental-utils/src/ts-eslint/SourceCode.ts +++ b/packages/experimental-utils/src/ts-eslint/SourceCode.ts @@ -1,4 +1,4 @@ -/* eslint-disable @typescript-eslint/no-namespace */ +/* eslint-disable @typescript-eslint/no-namespace, no-redeclare */ import { ParserServices, TSESTree } from '@typescript-eslint/typescript-estree'; import { SourceCode as ESLintSourceCode } from 'eslint'; diff --git a/packages/parser/package.json b/packages/parser/package.json index 1febddba372..d33fa890f50 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -43,8 +43,6 @@ "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@types/glob": "^7.1.1", - "@typescript-eslint/shared-fixtures": "1.9.0", - "glob": "^7.1.4" + "@typescript-eslint/shared-fixtures": "1.9.0" } } diff --git a/packages/parser/typings/eslint.d.ts b/packages/parser/typings/eslint.d.ts index c90febb3c47..15a965cd48d 100644 --- a/packages/parser/typings/eslint.d.ts +++ b/packages/parser/typings/eslint.d.ts @@ -1,6 +1,5 @@ declare module 'eslint/lib/util/traverser' { import { TSESTree } from '@typescript-eslint/experimental-utils'; - const traverser: { traverse( node: TSESTree.Node, diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 9c676a297f1..65b6b41440f 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -41,18 +41,7 @@ "semver": "5.5.0" }, "devDependencies": { - "@babel/code-frame": "7.0.0", - "@babel/parser": "7.3.2", "@babel/types": "^7.3.2", - "@types/glob": "^7.1.1", - "@types/babel-code-frame": "^6.20.1", - "@types/lodash.unescape": "^4.0.4", - "@types/lodash.isplainobject": "^4.0.4", - "@types/semver": "^5.5.0", - "@typescript-eslint/shared-fixtures": "1.9.0", - "babel-code-frame": "^6.26.0", - "lodash.isplainobject": "4.0.6", - "typescript": "*", - "glob": "^7.1.4" + "@typescript-eslint/shared-fixtures": "1.9.0" } } diff --git a/packages/typescript-estree/src/ast-converter.ts b/packages/typescript-estree/src/ast-converter.ts index 8488fa6f7d8..25d291dee4c 100644 --- a/packages/typescript-estree/src/ast-converter.ts +++ b/packages/typescript-estree/src/ast-converter.ts @@ -4,7 +4,7 @@ import { convertComments } from './convert-comments'; import { convertTokens } from './node-utils'; import { Extra } from './parser-options'; -export function astConverter( +export default function astConverter( ast: SourceFile, extra: Extra, shouldPreserveNodeMaps: boolean, diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 389c819d2a0..1a963859d96 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -1,6 +1,6 @@ import semver from 'semver'; import * as ts from 'typescript'; // leave this as * as ts so people using util package don't need syntheticDefaultImports -import { astConverter } from './ast-converter'; +import convert from './ast-converter'; import { convertError } from './convert'; import { firstDefined } from './node-utils'; import { Extra, TSESTreeOptions, ParserServices } from './parser-options'; @@ -50,7 +50,7 @@ function resetExtra(): void { strict: false, jsx: false, useJSXTextNode: false, - log: console.log, // eslint-disable-line no-console + log: console.log, projects: [], errorOnUnknownASTType: false, errorOnTypeScriptSyntacticAndSemanticIssues: false, @@ -339,7 +339,7 @@ export function parse( /** * Convert the TypeScript AST to an ESTree-compatible one */ - const { estree } = astConverter(ast, extra, false); + const { estree } = convert(ast, extra, false); return estree as AST; } @@ -397,7 +397,7 @@ export function parseAndGenerateServices< * Convert the TypeScript AST to an ESTree-compatible one, and optionally preserve * mappings between converted and original AST nodes */ - const { estree, astMaps } = astConverter(ast, extra, shouldPreserveNodeMaps); + const { estree, astMaps } = convert(ast, extra, shouldPreserveNodeMaps); /** * Even if TypeScript parsed the source code ok, and we had no problems converting the AST, * there may be other syntactic or semantic issues in the code that we can optionally report on. diff --git a/packages/typescript-estree/src/semantic-errors.ts b/packages/typescript-estree/src/semantic-errors.ts index c580697a185..f31eb634068 100644 --- a/packages/typescript-estree/src/semantic-errors.ts +++ b/packages/typescript-estree/src/semantic-errors.ts @@ -45,7 +45,7 @@ export function getFirstSemanticOrSyntacticError( * and log a a warning. */ /* istanbul ignore next */ - console.warn(`Warning From TSC: "${e.message}`); // eslint-disable-line no-console + console.warn(`Warning From TSC: "${e.message}`); /* istanbul ignore next */ return undefined; } diff --git a/packages/typescript-estree/tests/lib/parse.ts b/packages/typescript-estree/tests/lib/parse.ts index 3af2a91d327..3c34f705556 100644 --- a/packages/typescript-estree/tests/lib/parse.ts +++ b/packages/typescript-estree/tests/lib/parse.ts @@ -58,7 +58,7 @@ describe('parse()', () => { describe('loggerFn should be propagated to ast-converter', () => { it('output tokens, comments, locs, and ranges when called with those options', () => { - const spy = jest.spyOn(astConverter, 'astConverter'); + const spy = jest.spyOn(astConverter, 'default'); const loggerFn = jest.fn(() => true); diff --git a/tests/integration/utils/generate-package-json.js b/tests/integration/utils/generate-package-json.js index 1f6af28df63..bf173d5e35c 100644 --- a/tests/integration/utils/generate-package-json.js +++ b/tests/integration/utils/generate-package-json.js @@ -1,5 +1,4 @@ const fs = require('fs'); -// eslint-disable-next-line import/no-absolute-path const rootPackageJSON = require('/usr/root-package.json'); /** diff --git a/yarn.lock b/yarn.lock index bba6723622b..4f4a0d81f56 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1485,14 +1485,6 @@ array-ify@^1.0.0: resolved "https://registry.yarnpkg.com/array-ify/-/array-ify-1.0.0.tgz#9e528762b4a9066ad163a6962a364418e9626ece" integrity sha1-nlKHYrSpBmrRY6aWKjZEGOlibs4= -array-includes@^3.0.3: - version "3.0.3" - resolved "https://registry.yarnpkg.com/array-includes/-/array-includes-3.0.3.tgz#184b48f62d92d7452bb31b323165c7f8bd02266d" - integrity sha1-GEtI9i2S10UrsxsyMWXH+L0CJm0= - dependencies: - define-properties "^1.1.2" - es-abstract "^1.7.0" - array-union@^1.0.1: version "1.0.2" resolved "https://registry.yarnpkg.com/array-union/-/array-union-1.0.2.tgz#9a34410e4f4e3da23dea375be5be70f24778ec39" @@ -2084,11 +2076,6 @@ console-control-strings@^1.0.0, console-control-strings@~1.1.0: resolved "https://registry.yarnpkg.com/console-control-strings/-/console-control-strings-1.1.0.tgz#3d7cf4464db6446ea644bf4b39507f9851008e8e" integrity sha1-PXz0Rk22RG6mRL9LOVB/mFEAjo4= -contains-path@^0.1.0: - version "0.1.0" - resolved "https://registry.yarnpkg.com/contains-path/-/contains-path-0.1.0.tgz#fe8cf184ff6670b6baef01a9d4861a5cbec4120a" - integrity sha1-/ozxhP9mcLa67wGp1IYaXL7EEgo= - conventional-changelog-angular@^1.3.3: version "1.6.6" resolved "https://registry.yarnpkg.com/conventional-changelog-angular/-/conventional-changelog-angular-1.6.6.tgz#b27f2b315c16d0a1f23eb181309d0e6a4698ea0f" @@ -2356,7 +2343,7 @@ debug@3.1.0: dependencies: ms "2.0.0" -debug@^2.2.0, debug@^2.3.3, debug@^2.6.8, debug@^2.6.9: +debug@^2.2.0, debug@^2.3.3: version "2.6.9" resolved "https://registry.yarnpkg.com/debug/-/debug-2.6.9.tgz#5d128515df134ff327e90a4c93f4e077a536341f" integrity sha512-bC7ElrdJaJnPbAP+1EotYvqZsb3ecl5wi6Bfi6BJTUcNowp6cvspg0jXznRTKDjm/E7AdgFBVeAPVMNcKGsHMA== @@ -2531,14 +2518,6 @@ dir-glob@2.0.0: arrify "^1.0.1" path-type "^3.0.0" -doctrine@1.5.0: - version "1.5.0" - resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-1.5.0.tgz#379dce730f6166f76cefa4e6707a159b02c5a6fa" - integrity sha1-N53Ocw9hZvds76TmcHoVmwLFpvo= - dependencies: - esutils "^2.0.2" - isarray "^1.0.0" - doctrine@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/doctrine/-/doctrine-3.0.0.tgz#addebead72a6574db783639dc87a121773973961" @@ -2626,7 +2605,7 @@ error-ex@^1.2.0, error-ex@^1.3.1: dependencies: is-arrayish "^0.2.1" -es-abstract@^1.5.1, es-abstract@^1.7.0: +es-abstract@^1.5.1: version "1.13.0" resolved "https://registry.yarnpkg.com/es-abstract/-/es-abstract-1.13.0.tgz#ac86145fdd5099d8dd49558ccba2eaf9b88e24e9" integrity sha512-vDZfg/ykNxQVwup/8E1BZhVzFfBxs9NqMzGcvIJrqg5k2/5Za2bWo40dK2J1pgLngZ7c+Shh8lwYtLGyrwPutg== @@ -2688,52 +2667,11 @@ eslint-docs@^0.2.6: ora "^3.0.0" read-pkg-up "^4.0.0" -eslint-import-resolver-node@^0.3.2: - version "0.3.2" - resolved "https://registry.yarnpkg.com/eslint-import-resolver-node/-/eslint-import-resolver-node-0.3.2.tgz#58f15fb839b8d0576ca980413476aab2472db66a" - integrity sha512-sfmTqJfPSizWu4aymbPr4Iidp5yKm8yDkHp+Ir3YiTHiiDfxh69mOUsmiqW6RZ9zRXFaF64GtYmN7e+8GHBv6Q== - dependencies: - debug "^2.6.9" - resolve "^1.5.0" - -eslint-module-utils@^2.4.0: - version "2.4.0" - resolved "https://registry.yarnpkg.com/eslint-module-utils/-/eslint-module-utils-2.4.0.tgz#8b93499e9b00eab80ccb6614e69f03678e84e09a" - integrity sha512-14tltLm38Eu3zS+mt0KvILC3q8jyIAH518MlG+HO0p+yK885Lb1UHTY/UgR91eOyGdmxAPb+OLoW4znqIT6Ndw== - dependencies: - debug "^2.6.8" - pkg-dir "^2.0.0" - -eslint-plugin-eslint-comments@^3.1.1: - version "3.1.1" - resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-comments/-/eslint-plugin-eslint-comments-3.1.1.tgz#32ff0afba8a48e17073817e6d03fbc5622f735b7" - integrity sha512-GZDKhOFqJLKlaABX+kdoLskcTINMrVOWxGca54KcFb1QCPd0CLmqgAMRxkkUfGSmN+5NJUMGh7NGccIMcWPSfQ== - dependencies: - escape-string-regexp "^1.0.5" - ignore "^5.0.5" - eslint-plugin-eslint-plugin@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.0.1.tgz#d275434969dbde3da1d4cb7a121dc8d88457c786" integrity sha512-kJ5TZsRJH/xYstG07v3YeOy/W5SDAEzV+bvvoL0aiG1HtqDmg4mJvNPnn/JngANMmsx8oXlJrIcBTCpJzm+9kg== -eslint-plugin-import@^2.17.2: - version "2.17.2" - resolved "https://registry.yarnpkg.com/eslint-plugin-import/-/eslint-plugin-import-2.17.2.tgz#d227d5c6dc67eca71eb590d2bb62fb38d86e9fcb" - integrity sha512-m+cSVxM7oLsIpmwNn2WXTJoReOF9f/CtLMo7qOVmKd1KntBy0hEcuNZ3erTmWjx+DxRO0Zcrm5KwAvI9wHcV5g== - dependencies: - array-includes "^3.0.3" - contains-path "^0.1.0" - debug "^2.6.9" - doctrine "1.5.0" - eslint-import-resolver-node "^0.3.2" - eslint-module-utils "^2.4.0" - has "^1.0.3" - lodash "^4.17.11" - minimatch "^3.0.4" - read-pkg-up "^2.0.0" - resolve "^1.10.0" - eslint-plugin-jest@^22.2.2: version "22.5.1" resolved "https://registry.yarnpkg.com/eslint-plugin-jest/-/eslint-plugin-jest-22.5.1.tgz#a31dfe9f9513c6af7c17ece4c65535a1370f060b" @@ -3372,18 +3310,6 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.1.4: - version "7.1.4" - resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" - integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== - dependencies: - fs.realpath "^1.0.0" - inflight "^1.0.4" - inherits "2" - minimatch "^3.0.4" - once "^1.3.0" - path-is-absolute "^1.0.0" - global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" @@ -3608,11 +3534,6 @@ ignore@^4.0.6: resolved "https://registry.yarnpkg.com/ignore/-/ignore-4.0.6.tgz#750e3db5862087b4737ebac8207ffd1ef27b25fc" integrity sha512-cyFDKrqc/YdcWFniJhzI42+AzS+gNwmUzOSFcRCQYwySuBBBy/KjuxWLZ/FHEH6Moq1NizMOBWyTcv8O4OZIMg== -ignore@^5.0.5: - version "5.1.1" - resolved "https://registry.yarnpkg.com/ignore/-/ignore-5.1.1.tgz#2fc6b8f518aff48fef65a7f348ed85632448e4a5" - integrity sha512-DWjnQIFLenVrwyRCKZT+7a7/U4Cqgar4WG8V++K3hw+lrW1hc/SIwdiGmtxKCVACmHULTuGeBbHJmbwW7/sAvA== - import-fresh@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/import-fresh/-/import-fresh-2.0.0.tgz#d81355c15612d386c61f9ddd3922d4304822a546" @@ -3990,7 +3911,7 @@ is-wsl@^1.1.0: resolved "https://registry.yarnpkg.com/is-wsl/-/is-wsl-1.1.0.tgz#1f16e4aa22b04d1336b66188a66af3c600c3a66d" integrity sha1-HxbkqiKwTRM2tmGIpmrzxgDDpm0= -isarray@1.0.0, isarray@^1.0.0, isarray@~1.0.0: +isarray@1.0.0, isarray@~1.0.0: version "1.0.0" resolved "https://registry.yarnpkg.com/isarray/-/isarray-1.0.0.tgz#bb935d48582cba168c06834957a54a3e07124f11" integrity sha1-u5NdSFgsuhaMBoNJV6VKPgcSTxE= @@ -4770,16 +4691,6 @@ load-json-file@^1.0.0: pinkie-promise "^2.0.0" strip-bom "^2.0.0" -load-json-file@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-2.0.0.tgz#7947e42149af80d696cbf797bcaabcfe1fe29ca8" - integrity sha1-eUfkIUmvgNaWy/eXvKq8/h/inKg= - dependencies: - graceful-fs "^4.1.2" - parse-json "^2.2.0" - pify "^2.0.0" - strip-bom "^3.0.0" - load-json-file@^4.0.0: version "4.0.0" resolved "https://registry.yarnpkg.com/load-json-file/-/load-json-file-4.0.0.tgz#2f5f45ab91e33216234fd53adab668eb4ec0993b" @@ -5894,13 +5805,6 @@ path-type@^1.0.0: pify "^2.0.0" pinkie-promise "^2.0.0" -path-type@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/path-type/-/path-type-2.0.0.tgz#f012ccb8415b7096fc2daa1054c3d72389594c73" - integrity sha1-8BLMuEFbcJb8LaoQVMPXI4lZTHM= - dependencies: - pify "^2.0.0" - path-type@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/path-type/-/path-type-3.0.0.tgz#cef31dc8e0a1a3bb0d105c0cd97cf3bf47f4e36f" @@ -6174,14 +6078,6 @@ read-pkg-up@^1.0.1: find-up "^1.0.0" read-pkg "^1.0.0" -read-pkg-up@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-2.0.0.tgz#6b72a8048984e0c41e79510fd5e9fa99b3b549be" - integrity sha1-a3KoBImE4MQeeVEP1en6mbO1Sb4= - dependencies: - find-up "^2.0.0" - read-pkg "^2.0.0" - read-pkg-up@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg-up/-/read-pkg-up-3.0.0.tgz#3ed496685dba0f8fe118d0691dc51f4a1ff96f07" @@ -6207,15 +6103,6 @@ read-pkg@^1.0.0: normalize-package-data "^2.3.2" path-type "^1.0.0" -read-pkg@^2.0.0: - version "2.0.0" - resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-2.0.0.tgz#8ef1c0623c6a6db0dc6713c4bfac46332b2368f8" - integrity sha1-jvHAYjxqbbDcZxPEv6xGMysjaPg= - dependencies: - load-json-file "^2.0.0" - normalize-package-data "^2.3.2" - path-type "^2.0.0" - read-pkg@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/read-pkg/-/read-pkg-3.0.0.tgz#9cbc686978fee65d16c00e2b19c237fcf6e38389" @@ -6442,7 +6329,7 @@ resolve@1.1.7: resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.1.7.tgz#203114d82ad2c5ed9e8e0411b3932875e889e97b" integrity sha1-IDEU2CrSxe2ejgQRs5ModeiJ6Xs= -resolve@1.x, resolve@^1.10.0, resolve@^1.3.2, resolve@^1.5.0: +resolve@1.x, resolve@^1.10.0, resolve@^1.3.2: version "1.10.1" resolved "https://registry.yarnpkg.com/resolve/-/resolve-1.10.1.tgz#664842ac960795bbe758221cdccda61fb64b5f18" integrity sha512-KuIe4mf++td/eFb6wkaPbMDnP6kObCaEtIDuHOUED6MNUo4K670KZUHuuvYPZDxNF0WVLw49n06M2m2dXphEzA== @@ -7303,7 +7190,7 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -typescript@*, "typescript@>=3.2.1 <3.5.0": +"typescript@>=3.2.1 <3.5.0": version "3.4.5" resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== From 4edf0d71ef0a8a3370165e804dd693033d6207bc Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Wed, 15 May 2019 21:27:03 -0700 Subject: [PATCH 24/41] chore: Add documentation validation tool (#531) --- .prettierignore | 1 + azure-pipelines.yml | 4 + package.json | 1 + packages/eslint-plugin/README.md | 90 ++++++------- packages/eslint-plugin/package.json | 10 +- packages/eslint-plugin/src/configs/all.json | 5 + .../src/configs/recommended.json | 1 + .../src/rules/adjacent-overload-signatures.ts | 1 - .../eslint-plugin/src/rules/array-type.ts | 1 - .../eslint-plugin/src/rules/await-thenable.ts | 1 - .../eslint-plugin/src/rules/ban-ts-ignore.ts | 3 +- packages/eslint-plugin/src/rules/ban-types.ts | 1 - .../src/rules/class-name-casing.ts | 1 - .../rules/explicit-member-accessibility.ts | 1 - .../src/rules/func-call-spacing.ts | 2 +- packages/eslint-plugin/src/rules/indent.ts | 1 - .../src/rules/interface-name-prefix.ts | 1 - .../eslint-plugin/src/rules/member-naming.ts | 2 +- .../src/rules/member-ordering.ts | 1 - .../rules/no-angle-bracket-type-assertion.ts | 1 - .../src/rules/no-empty-interface.ts | 1 - .../src/rules/no-explicit-any.ts | 1 - .../src/rules/no-extra-parens.ts | 2 +- .../src/rules/no-extraneous-class.ts | 1 - .../src/rules/no-for-in-array.ts | 1 - .../src/rules/no-inferrable-types.ts | 3 +- .../src/rules/no-magic-numbers.ts | 7 +- .../eslint-plugin/src/rules/no-misused-new.ts | 3 +- .../eslint-plugin/src/rules/no-namespace.ts | 1 - .../src/rules/no-non-null-assertion.ts | 1 - .../rules/no-object-literal-type-assertion.ts | 1 - .../src/rules/no-parameter-properties.ts | 3 +- .../src/rules/no-require-imports.ts | 3 +- .../eslint-plugin/src/rules/no-this-alias.ts | 1 - .../src/rules/no-triple-slash-reference.ts | 1 - .../eslint-plugin/src/rules/no-type-alias.ts | 1 - .../src/rules/no-unnecessary-qualifier.ts | 3 +- .../rules/no-unnecessary-type-assertion.ts | 1 - .../eslint-plugin/src/rules/no-unused-vars.ts | 1 - .../src/rules/no-var-requires.ts | 1 - .../eslint-plugin/src/rules/prefer-for-of.ts | 3 +- .../src/rules/prefer-function-type.ts | 1 - .../src/rules/prefer-interface.ts | 1 - .../src/rules/prefer-namespace-keyword.ts | 3 +- .../src/rules/prefer-regexp-exec.ts | 2 +- .../rules/prefer-string-starts-ends-with.ts | 2 +- .../src/rules/promise-function-async.ts | 3 +- .../src/rules/restrict-plus-operands.ts | 3 +- .../src/rules/type-annotation-spacing.ts | 1 - .../eslint-plugin/src/rules/unbound-method.ts | 3 +- .../src/rules/unified-signatures.ts | 3 +- .../eslint-plugin/tools/generate-configs.ts | 34 ++++- .../validate-docs/check-for-rule-docs.ts | 27 ++++ .../tools/validate-docs/index.ts | 34 +++++ .../eslint-plugin/tools/validate-docs/log.ts | 20 +++ .../tools/validate-docs/parse-readme.ts | 29 +++++ .../validate-docs/validate-table-rules.ts | 123 ++++++++++++++++++ .../validate-docs/validate-table-structure.ts | 48 +++++++ .../src/eslint-utils/RuleCreator.ts | 7 +- yarn.lock | 73 ++--------- 60 files changed, 405 insertions(+), 181 deletions(-) create mode 100644 packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts create mode 100644 packages/eslint-plugin/tools/validate-docs/index.ts create mode 100644 packages/eslint-plugin/tools/validate-docs/log.ts create mode 100644 packages/eslint-plugin/tools/validate-docs/parse-readme.ts create mode 100644 packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts create mode 100644 packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts diff --git a/.prettierignore b/.prettierignore index 755a2771d25..2b05b38a1ee 100644 --- a/.prettierignore +++ b/.prettierignore @@ -7,3 +7,4 @@ **/.nyc_output packages/eslint-plugin-tslint/tests/test-tslint-rules-directory/alwaysFailRule.js .github +packages/eslint-plugin/src/configs/*.json diff --git a/azure-pipelines.yml b/azure-pipelines.yml index 911aa93bb3c..8dcbd9ea422 100644 --- a/azure-pipelines.yml +++ b/azure-pipelines.yml @@ -31,6 +31,10 @@ jobs: yarn lint displayName: 'Run linting' + - script: | + yarn docs:check + displayName: 'Validate documentation' + - script: | yarn test displayName: 'Run unit tests' diff --git a/package.json b/package.json index 8d9760c2c25..a8ac88c3719 100644 --- a/package.json +++ b/package.json @@ -20,6 +20,7 @@ "build": "lerna run build", "clean": "lerna clean && lerna run clean", "cz": "git-cz", + "docs:check": "lerna run docs:check", "generate-contributors": "yarn ts-node ./tools/generate-contributors.ts && yarn all-contributors generate", "format": "prettier --write \"./**/*.{ts,js,json,md}\"", "format-check": "prettier --list-different \"./**/*.{ts,js,json,md}\"", diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 24ada799b9a..18dc3a6e6cd 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -110,59 +110,59 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | Name | Description | :heavy_check_mark: | :wrench: | :thought_balloon: | | --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | ----------------- | -| [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallow awaiting a value that is not a Promise (`await-promise` from TSLint) | | | :thought_balloon: | -| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used (`ban-ts-ignore` from TSLint) | | | | -| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive | :heavy_check_mark: | | | +| [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/await-thenable`](./docs/rules/await-thenable.md) | Disallows awaiting a value that is not a Thenable | | | :thought_balloon: | +| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used | | | | +| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | -| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names (`class-name` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names | :heavy_check_mark: | | | | [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | -| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Spacing between function identifiers and their invocations | | :wrench: | | +| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods | :heavy_check_mark: | | | +| [`@typescript-eslint/func-call-spacing`](./docs/rules/func-call-spacing.md) | Require or disallow spacing between function identifiers and their invocations | | :wrench: | | | [`@typescript-eslint/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | | -| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation (`indent` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` | :heavy_check_mark: | | | | [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | | -| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | | -| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions (`no-angle-bracket-type-assertion` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility | | | | +| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order | | | | +| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions | :heavy_check_mark: | | | | [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces | :heavy_check_mark: | | | +| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | | | | [`@typescript-eslint/no-extra-parens`](./docs/rules/no-extra-parens.md) | Disallow unnecessary parentheses | | :wrench: | | -| [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | | | -| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop (`no-for-in-array` from TSLint) | | | :thought_balloon: | -| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallows magic numbers. | :heavy_check_mark: | | -| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` (`no-require-imports` from TSLint) | | | | -| [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` (`no-this-assignment` from TSLint) | | | | -| [`@typescript-eslint/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments (`no-reference` from TSLint) | :heavy_check_mark: | | | -| [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases (`interface-over-type-literal` from TSLint) | | | | -| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary (`no-unnecessary-qualifier` from TSLint) | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression (`no-unnecessary-type-assertion` from TSLint) | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables (`no-unused-variable` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces | | | | +| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop | | | :thought_balloon: | +| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/no-magic-numbers`](./docs/rules/no-magic-numbers.md) | Disallows magic numbers | | | | +| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor` | :heavy_check_mark: | | | +| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces | :heavy_check_mark: | | | +| [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator | :heavy_check_mark: | | | +| [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression | :heavy_check_mark: | | | +| [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors | :heavy_check_mark: | | | +| [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` | | | | +| [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` | | | | +| [`@typescript-eslint/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments | :heavy_check_mark: | | | +| [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases | | | | +| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables | :heavy_check_mark: | | | | [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | | | [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | | -| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | | | -| [`@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. | | | | -| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures (`callable-types` from TSLint) | | :wrench: | | -| [`@typescript-eslint/prefer-includes`](./docs/rules/prefer-includes.md) | Enforce `includes` method over `indexOf` method. | | :wrench: | :thought_balloon: | -| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | | :wrench: | | -| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | | | :thought_balloon: | -| [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | | -| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | | :thought_balloon: | +| [`@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-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 | | | | +| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures | | :wrench: | | +| [`@typescript-eslint/prefer-includes`](./docs/rules/prefer-includes.md) | Enforce `includes` method over `indexOf` method | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Prefer RegExp#exec() over String#match() if no global flag is provided | | | :thought_balloon: | +| [`@typescript-eslint/prefer-string-starts-ends-with`](./docs/rules/prefer-string-starts-ends-with.md) | Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async | | | :thought_balloon: | +| [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | :thought_balloon: | +| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string | | | :thought_balloon: | | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | -| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | | -| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | | | :thought_balloon: | -| [`@typescript-eslint/prefer-regexp-exec`](./docs/rules/prefer-regexp-exec.md) | Enforce to use `RegExp#exec` over `String#match` | | | :thought_balloon: | -| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one. (`unified-signatures` from TSLint) | | | | +| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope | | | :thought_balloon: | +| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter | | | | diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index a52aacc2352..bbefaa09b00 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -28,12 +28,12 @@ "build": "tsc -p tsconfig.build.json", "clean": "rimraf dist/", "docs": "eslint-docs", - "docs:check": "eslint-docs check", - "test": "jest --coverage", - "generate:configs": "ts-node --files tools/generate-configs.ts", + "docs:check": "ts-node --files ./tools/validate-docs/index.ts", "format": "prettier --write \"./**/*.{ts,js,json,md}\" --ignore-path ../../.prettierignore", + "generate:configs": "ts-node --files tools/generate-configs.ts", "prebuild": "npm run clean", "recommended:update": "ts-node tools/update-recommended.ts", + "test": "jest --coverage", "typecheck": "tsc --noEmit" }, "dependencies": { @@ -44,7 +44,9 @@ "tsutils": "^3.7.0" }, "devDependencies": { - "eslint-docs": "^0.2.6" + "@types/marked": "^0.6.5", + "chalk": "^2.4.2", + "marked": "^0.6.2" }, "peerDependencies": { "@typescript-eslint/parser": "1.9.0", diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index fa36e3d876c..cc2f9778671 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -11,6 +11,7 @@ "@typescript-eslint/class-name-casing": "error", "@typescript-eslint/explicit-function-return-type": "error", "@typescript-eslint/explicit-member-accessibility": "error", + "func-call-spacing": "off", "@typescript-eslint/func-call-spacing": "error", "@typescript-eslint/generic-type-naming": "error", "indent": "off", @@ -24,10 +25,12 @@ "@typescript-eslint/no-array-constructor": "error", "@typescript-eslint/no-empty-interface": "error", "@typescript-eslint/no-explicit-any": "error", + "no-extra-parens": "off", "@typescript-eslint/no-extra-parens": "error", "@typescript-eslint/no-extraneous-class": "error", "@typescript-eslint/no-for-in-array": "error", "@typescript-eslint/no-inferrable-types": "error", + "no-magic-numbers": "off", "@typescript-eslint/no-magic-numbers": "error", "@typescript-eslint/no-misused-new": "error", "@typescript-eslint/no-namespace": "error", @@ -42,6 +45,7 @@ "@typescript-eslint/no-unnecessary-type-assertion": "error", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "error", + "no-use-before-define": "off", "@typescript-eslint/no-use-before-define": "error", "no-useless-constructor": "off", "@typescript-eslint/no-useless-constructor": "error", @@ -56,6 +60,7 @@ "@typescript-eslint/promise-function-async": "error", "@typescript-eslint/require-array-sort-compare": "error", "@typescript-eslint/restrict-plus-operands": "error", + "semi": "off", "@typescript-eslint/semi": "error", "@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/unbound-method": "error", diff --git a/packages/eslint-plugin/src/configs/recommended.json b/packages/eslint-plugin/src/configs/recommended.json index 12edb521fe8..d26fd25d01c 100644 --- a/packages/eslint-plugin/src/configs/recommended.json +++ b/packages/eslint-plugin/src/configs/recommended.json @@ -27,6 +27,7 @@ "@typescript-eslint/no-triple-slash-reference": "error", "no-unused-vars": "off", "@typescript-eslint/no-unused-vars": "warn", + "no-use-before-define": "off", "@typescript-eslint/no-use-before-define": "error", "@typescript-eslint/no-var-requires": "error", "@typescript-eslint/prefer-interface": "error", diff --git a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts index 80c8b131587..b2f22b8a0aa 100644 --- a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts +++ b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts @@ -19,7 +19,6 @@ export default util.createRule({ docs: { description: 'Require that member overloads be consecutive', category: 'Best Practices', - tslintName: 'adjacent-overload-signatures', recommended: 'error', }, schema: [], diff --git a/packages/eslint-plugin/src/rules/array-type.ts b/packages/eslint-plugin/src/rules/array-type.ts index 62ace5d4317..12b425fdd54 100644 --- a/packages/eslint-plugin/src/rules/array-type.ts +++ b/packages/eslint-plugin/src/rules/array-type.ts @@ -85,7 +85,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Requires using either `T[]` or `Array` for arrays', - tslintRuleName: 'array-type', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/await-thenable.ts b/packages/eslint-plugin/src/rules/await-thenable.ts index f9981920fab..7a5db98db9c 100644 --- a/packages/eslint-plugin/src/rules/await-thenable.ts +++ b/packages/eslint-plugin/src/rules/await-thenable.ts @@ -10,7 +10,6 @@ export default util.createRule({ description: 'Disallows awaiting a value that is not a Thenable', category: 'Best Practices', recommended: false, - tslintName: 'await-thenable', }, messages: { await: 'Unexpected `await` of a non-Promise (non-"Thenable") value.', diff --git a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts index 661031a03f7..87af895627d 100644 --- a/packages/eslint-plugin/src/rules/ban-ts-ignore.ts +++ b/packages/eslint-plugin/src/rules/ban-ts-ignore.ts @@ -5,8 +5,7 @@ export default util.createRule({ meta: { type: 'problem', docs: { - description: 'Bans “// @ts-ignore” comments from being used.', - tslintRuleName: 'ban-ts-ignore', + description: 'Bans “// @ts-ignore” comments from being used', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 25c0a08f3ab..059f7302024 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -26,7 +26,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Enforces that types will not to be used', - tslintRuleName: 'ban-types', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/class-name-casing.ts b/packages/eslint-plugin/src/rules/class-name-casing.ts index 829f29f24b8..1c76edd118b 100644 --- a/packages/eslint-plugin/src/rules/class-name-casing.ts +++ b/packages/eslint-plugin/src/rules/class-name-casing.ts @@ -10,7 +10,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Require PascalCased class and interface names', - tslintRuleName: 'class-name', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index d760f18ea94..4fea64e99eb 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -33,7 +33,6 @@ export default util.createRule({ docs: { description: 'Require explicit accessibility modifiers on class properties and methods', - tslintRuleName: 'member-access', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/func-call-spacing.ts b/packages/eslint-plugin/src/rules/func-call-spacing.ts index fd395887480..b6420781654 100644 --- a/packages/eslint-plugin/src/rules/func-call-spacing.ts +++ b/packages/eslint-plugin/src/rules/func-call-spacing.ts @@ -16,7 +16,7 @@ export default util.createRule({ type: 'layout', docs: { description: - 'require or disallow spacing between function identifiers and their invocations', + 'Require or disallow spacing between function identifiers and their invocations', category: 'Stylistic Issues', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts index 6a7a5c31e13..6b2a83066dd 100644 --- a/packages/eslint-plugin/src/rules/indent.ts +++ b/packages/eslint-plugin/src/rules/indent.ts @@ -88,7 +88,6 @@ export default util.createRule({ type: 'layout', docs: { description: 'Enforce consistent indentation', - tslintRuleName: 'indent', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/interface-name-prefix.ts b/packages/eslint-plugin/src/rules/interface-name-prefix.ts index b9c3b91a052..1af78f79a53 100644 --- a/packages/eslint-plugin/src/rules/interface-name-prefix.ts +++ b/packages/eslint-plugin/src/rules/interface-name-prefix.ts @@ -9,7 +9,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Require that interface names be prefixed with `I`', - tslintRuleName: 'interface-name', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/member-naming.ts b/packages/eslint-plugin/src/rules/member-naming.ts index 7c221360c0c..9995b6cf1e3 100644 --- a/packages/eslint-plugin/src/rules/member-naming.ts +++ b/packages/eslint-plugin/src/rules/member-naming.ts @@ -16,7 +16,7 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Enforces naming conventions for class members by visibility.', + 'Enforces naming conventions for class members by visibility', category: 'Stylistic Issues', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index ffcc6985073..25dd2f79ca1 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -46,7 +46,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Require a consistent member declaration order', - tslintRuleName: 'member-ordering', category: 'Stylistic Issues', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts b/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts index 630847ff57a..82848a07196 100644 --- a/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-angle-bracket-type-assertion.ts @@ -7,7 +7,6 @@ export default util.createRule({ docs: { description: 'Enforces the use of `as Type` assertions instead of `` assertions', - tslintRuleName: 'no-angle-bracket-type-assertion', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-empty-interface.ts b/packages/eslint-plugin/src/rules/no-empty-interface.ts index 3bf4d72a622..277e9ebb313 100644 --- a/packages/eslint-plugin/src/rules/no-empty-interface.ts +++ b/packages/eslint-plugin/src/rules/no-empty-interface.ts @@ -13,7 +13,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow the declaration of empty interfaces', - tslintRuleName: 'no-empty-interface', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index 7b27ee0bcd8..541b0acb1df 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -6,7 +6,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow usage of the `any` type', - tslintRuleName: 'no-any', category: 'Best Practices', recommended: 'warn', }, diff --git a/packages/eslint-plugin/src/rules/no-extra-parens.ts b/packages/eslint-plugin/src/rules/no-extra-parens.ts index 1b12e38e6ec..8f94fc1fa9d 100644 --- a/packages/eslint-plugin/src/rules/no-extra-parens.ts +++ b/packages/eslint-plugin/src/rules/no-extra-parens.ts @@ -14,7 +14,7 @@ export default util.createRule({ meta: { type: 'layout', docs: { - description: 'disallow unnecessary parentheses', + description: 'Disallow unnecessary parentheses', category: 'Possible Errors', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-extraneous-class.ts b/packages/eslint-plugin/src/rules/no-extraneous-class.ts index b0917b5ae72..29928b6709d 100644 --- a/packages/eslint-plugin/src/rules/no-extraneous-class.ts +++ b/packages/eslint-plugin/src/rules/no-extraneous-class.ts @@ -19,7 +19,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Forbids the use of classes as namespaces', - tslintRuleName: 'no-unnecessary-class', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-for-in-array.ts b/packages/eslint-plugin/src/rules/no-for-in-array.ts index b61bc7ca9df..970c621887e 100644 --- a/packages/eslint-plugin/src/rules/no-for-in-array.ts +++ b/packages/eslint-plugin/src/rules/no-for-in-array.ts @@ -8,7 +8,6 @@ export default util.createRule({ description: 'Disallow iterating over an array with a for-in loop', category: 'Best Practices', recommended: false, - tslintName: 'no-for-in-array', }, messages: { forInViolation: diff --git a/packages/eslint-plugin/src/rules/no-inferrable-types.ts b/packages/eslint-plugin/src/rules/no-inferrable-types.ts index 3546cd1112d..e9db74cab3f 100644 --- a/packages/eslint-plugin/src/rules/no-inferrable-types.ts +++ b/packages/eslint-plugin/src/rules/no-inferrable-types.ts @@ -18,8 +18,7 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean.', - tslintRuleName: 'no-inferrable-types', + 'Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-magic-numbers.ts b/packages/eslint-plugin/src/rules/no-magic-numbers.ts index 49689213992..f298bfd68f2 100644 --- a/packages/eslint-plugin/src/rules/no-magic-numbers.ts +++ b/packages/eslint-plugin/src/rules/no-magic-numbers.ts @@ -1,8 +1,3 @@ -/** - * @fileoverview Rule to flag statements that use magic numbers (adapted from https://github.com/danielstjules/buddy.js) - * @author Scott O'Hara - */ - import { TSESTree, AST_NODE_TYPES, @@ -21,7 +16,7 @@ export default util.createRule({ meta: { type: 'suggestion', docs: { - description: 'Disallow magic numbers', + description: 'Disallows magic numbers', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-misused-new.ts b/packages/eslint-plugin/src/rules/no-misused-new.ts index 56c5eb5f296..aa9d0cb366c 100644 --- a/packages/eslint-plugin/src/rules/no-misused-new.ts +++ b/packages/eslint-plugin/src/rules/no-misused-new.ts @@ -9,8 +9,7 @@ export default util.createRule({ meta: { type: 'problem', docs: { - description: 'Enforce valid definition of `new` and `constructor`.', - tslintRuleName: 'no-misused-new', + description: 'Enforce valid definition of `new` and `constructor`', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-namespace.ts b/packages/eslint-plugin/src/rules/no-namespace.ts index 2ab66102dd4..930b3067eba 100644 --- a/packages/eslint-plugin/src/rules/no-namespace.ts +++ b/packages/eslint-plugin/src/rules/no-namespace.ts @@ -19,7 +19,6 @@ export default util.createRule({ docs: { description: 'Disallow the use of custom TypeScript modules and namespaces', - tslintRuleName: 'no-namespace', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts index 58c0667cf1c..6ed336e4d2d 100644 --- a/packages/eslint-plugin/src/rules/no-non-null-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-non-null-assertion.ts @@ -7,7 +7,6 @@ export default util.createRule({ docs: { description: 'Disallows non-null assertions using the `!` postfix operator', - tslintRuleName: 'no-non-null-assertion', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts index d946c879257..f4089655337 100644 --- a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts @@ -18,7 +18,6 @@ export default util.createRule({ docs: { description: 'Forbids an object literal to appear in a type assertion expression', - tslintRuleName: 'no-object-literal-type-assertion', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-parameter-properties.ts b/packages/eslint-plugin/src/rules/no-parameter-properties.ts index 6d1ffa80870..2ef515b721f 100644 --- a/packages/eslint-plugin/src/rules/no-parameter-properties.ts +++ b/packages/eslint-plugin/src/rules/no-parameter-properties.ts @@ -25,8 +25,7 @@ export default util.createRule({ type: 'problem', docs: { description: - 'Disallow the use of parameter properties in class constructors.', - tslintRuleName: 'no-parameter-properties', + 'Disallow the use of parameter properties in class constructors', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-require-imports.ts b/packages/eslint-plugin/src/rules/no-require-imports.ts index 8bed451ccb7..afb0b4a3848 100644 --- a/packages/eslint-plugin/src/rules/no-require-imports.ts +++ b/packages/eslint-plugin/src/rules/no-require-imports.ts @@ -6,8 +6,7 @@ export default util.createRule({ meta: { type: 'problem', docs: { - description: 'Disallows invocation of `require()`.', - tslintName: 'no-require-imports', + description: 'Disallows invocation of `require()`', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-this-alias.ts b/packages/eslint-plugin/src/rules/no-this-alias.ts index 30201acf6a0..6b913539903 100644 --- a/packages/eslint-plugin/src/rules/no-this-alias.ts +++ b/packages/eslint-plugin/src/rules/no-this-alias.ts @@ -18,7 +18,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow aliasing `this`', - tslintRuleName: 'no-this-assignment', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts b/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts index 6f596cd03d7..c7780a99bf3 100644 --- a/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts @@ -6,7 +6,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow `/// ` comments', - tslintRuleName: 'no-reference', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index b4a4274c274..92249006455 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -36,7 +36,6 @@ export default util.createRule({ type: 'suggestion', docs: { description: 'Disallow the use of type aliases', - tslintRuleName: 'interface-over-type-literal', category: 'Stylistic Issues', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts index f382e7262e0..23a98cb9bef 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-qualifier.ts @@ -8,9 +8,8 @@ export default util.createRule({ meta: { docs: { category: 'Best Practices', - description: 'Warns when a namespace qualifier is unnecessary.', + description: 'Warns when a namespace qualifier is unnecessary', recommended: false, - tslintName: 'no-unnecessary-qualifier', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index c98b619eab4..765fd308653 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -28,7 +28,6 @@ export default util.createRule({ 'Warns if a type assertion does not change the type of an expression', category: 'Best Practices', recommended: false, - tslintRuleName: 'no-unnecessary-type-assertion', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/no-unused-vars.ts b/packages/eslint-plugin/src/rules/no-unused-vars.ts index 9fc32f6fc1c..46084af3a6b 100644 --- a/packages/eslint-plugin/src/rules/no-unused-vars.ts +++ b/packages/eslint-plugin/src/rules/no-unused-vars.ts @@ -11,7 +11,6 @@ export default util.createRule({ type: 'problem', docs: { description: 'Disallow unused variables', - tslintRuleName: 'no-unused-variable', category: 'Variables', recommended: 'warn', }, diff --git a/packages/eslint-plugin/src/rules/no-var-requires.ts b/packages/eslint-plugin/src/rules/no-var-requires.ts index a8acccd8af9..8d8e714c4f5 100644 --- a/packages/eslint-plugin/src/rules/no-var-requires.ts +++ b/packages/eslint-plugin/src/rules/no-var-requires.ts @@ -11,7 +11,6 @@ export default util.createRule({ docs: { description: 'Disallows the use of require statements except in import statements', - tslintRuleName: 'no-var-requires', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts index 8e829b48e9c..19551a91166 100644 --- a/packages/eslint-plugin/src/rules/prefer-for-of.ts +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -11,10 +11,9 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated.', + 'Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated', category: 'Stylistic Issues', recommended: false, - tslintName: 'prefer-for-of', }, messages: { preferForOf: diff --git a/packages/eslint-plugin/src/rules/prefer-function-type.ts b/packages/eslint-plugin/src/rules/prefer-function-type.ts index 95f1a8ee3ca..f375b1bb8f2 100644 --- a/packages/eslint-plugin/src/rules/prefer-function-type.ts +++ b/packages/eslint-plugin/src/rules/prefer-function-type.ts @@ -13,7 +13,6 @@ export default util.createRule({ 'Use function types instead of interfaces with call signatures', category: 'Best Practices', recommended: false, - tslintName: 'callable-types', }, fixable: 'code', messages: { diff --git a/packages/eslint-plugin/src/rules/prefer-interface.ts b/packages/eslint-plugin/src/rules/prefer-interface.ts index 6efbf62d6a6..3197d579540 100644 --- a/packages/eslint-plugin/src/rules/prefer-interface.ts +++ b/packages/eslint-plugin/src/rules/prefer-interface.ts @@ -8,7 +8,6 @@ export default util.createRule({ docs: { description: 'Prefer an interface declaration over a type literal (type T = { ... })', - tslintRuleName: 'interface-over-type-literal', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts index 9d60e0eed4f..6059a731a94 100644 --- a/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts +++ b/packages/eslint-plugin/src/rules/prefer-namespace-keyword.ts @@ -10,8 +10,7 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules.', - tslintRuleName: 'no-internal-module', + 'Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules', category: 'Best Practices', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts index ea4e6742d05..ffcf5aef975 100644 --- a/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts +++ b/packages/eslint-plugin/src/rules/prefer-regexp-exec.ts @@ -10,7 +10,7 @@ export default createRule({ type: 'suggestion', docs: { description: - 'Prefer RegExp#exec() over String#match() if no global flag is provided.', + 'Prefer RegExp#exec() over String#match() if no global flag is provided', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts index ce45e860068..67b341a8e50 100644 --- a/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts +++ b/packages/eslint-plugin/src/rules/prefer-string-starts-ends-with.ts @@ -18,7 +18,7 @@ export default createRule({ type: 'suggestion', docs: { description: - 'enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings', + 'Enforce the use of `String#startsWith` and `String#endsWith` instead of other equivalent methods of checking substrings', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/promise-function-async.ts b/packages/eslint-plugin/src/rules/promise-function-async.ts index ef041758630..885580c493f 100644 --- a/packages/eslint-plugin/src/rules/promise-function-async.ts +++ b/packages/eslint-plugin/src/rules/promise-function-async.ts @@ -18,8 +18,7 @@ export default util.createRule({ type: 'suggestion', docs: { description: - 'Requires any function or method that returns a Promise to be marked async.', - tslintName: 'promise-function-async', + 'Requires any function or method that returns a Promise to be marked async', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts index 9a3a42c8f32..6d68d5f7dfc 100644 --- a/packages/eslint-plugin/src/rules/restrict-plus-operands.ts +++ b/packages/eslint-plugin/src/rules/restrict-plus-operands.ts @@ -8,8 +8,7 @@ export default util.createRule({ type: 'problem', docs: { description: - 'When adding two variables, operands must both be of type number or of type string.', - tslintName: 'restrict-plus-operands', + 'When adding two variables, operands must both be of type number or of type string', category: 'Best Practices', recommended: false, }, diff --git a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts index 19ef206119f..e270c0f7fec 100644 --- a/packages/eslint-plugin/src/rules/type-annotation-spacing.ts +++ b/packages/eslint-plugin/src/rules/type-annotation-spacing.ts @@ -38,7 +38,6 @@ export default util.createRule({ type: 'layout', docs: { description: 'Require consistent spacing around type annotations', - tslintRuleName: 'typedef-whitespace', category: 'Stylistic Issues', recommended: 'error', }, diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts index cb9e388370e..88892b63417 100644 --- a/packages/eslint-plugin/src/rules/unbound-method.ts +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -24,8 +24,7 @@ export default util.createRule({ docs: { category: 'Best Practices', description: - 'Enforces unbound methods are called with their expected scope.', - tslintName: 'no-unbound-method', + 'Enforces unbound methods are called with their expected scope', recommended: false, }, messages: { diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts index d4ea472be5e..04695fcc302 100644 --- a/packages/eslint-plugin/src/rules/unified-signatures.ts +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -53,10 +53,9 @@ export default util.createRule({ meta: { docs: { description: - 'Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter.', + 'Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter', category: 'Variables', recommended: false, - tslintName: 'unified-signatures', }, type: 'suggestion', messages: { diff --git a/packages/eslint-plugin/tools/generate-configs.ts b/packages/eslint-plugin/tools/generate-configs.ts index 9809adc05c7..322c4aedae1 100644 --- a/packages/eslint-plugin/tools/generate-configs.ts +++ b/packages/eslint-plugin/tools/generate-configs.ts @@ -1,6 +1,7 @@ /* eslint-disable no-console */ import { TSESLint } from '@typescript-eslint/experimental-utils'; +import chalk from 'chalk'; import fs from 'fs'; import path from 'path'; import rules from '../src/rules'; @@ -17,14 +18,19 @@ interface LinterConfig extends TSESLint.Linter.Config { } const RULE_NAME_PREFIX = '@typescript-eslint/'; -const MAX_RULE_NAME_LENGTH = 32 + RULE_NAME_PREFIX.length; +const MAX_RULE_NAME_LENGTH = 32; const DEFAULT_RULE_SETTING = 'warn'; const BASE_RULES_TO_BE_OVERRIDDEN = new Set([ 'camelcase', + 'func-call-spacing', 'indent', 'no-array-constructor', + 'no-extra-parens', + 'no-magic-numbers', 'no-unused-vars', + 'no-use-before-define', 'no-useless-constructor', + 'semi', ]); const ruleEntries = Object.entries(rules); @@ -58,10 +64,22 @@ const reducer = ( : recommendation; if (BASE_RULES_TO_BE_OVERRIDDEN.has(key)) { - console.log(key.padEnd(MAX_RULE_NAME_LENGTH), '=', 'off'); + console.log( + key + .padStart(RULE_NAME_PREFIX.length + key.length) + .padEnd(RULE_NAME_PREFIX.length + MAX_RULE_NAME_LENGTH), + '=', + chalk.green('off'), + ); config[key] = 'off'; } - console.log(ruleName.padEnd(MAX_RULE_NAME_LENGTH), '=', usedSetting); + console.log( + `${chalk.dim(RULE_NAME_PREFIX)}${key.padEnd(MAX_RULE_NAME_LENGTH)}`, + '=', + usedSetting === 'error' + ? chalk.red(usedSetting) + : chalk.yellow(usedSetting), + ); config[ruleName] = usedSetting; return config; @@ -83,7 +101,10 @@ const baseConfig: LinterConfig = { }; writeConfig(baseConfig, path.resolve(__dirname, '../src/configs/base.json')); -console.log('------------------------- all.json -------------------------'); +console.log(); +console.log( + '---------------------------------- all.json ----------------------------------', +); const allConfig: LinterConfig = { extends: './configs/base.json', rules: ruleEntries.reduce( @@ -94,7 +115,10 @@ const allConfig: LinterConfig = { }; writeConfig(allConfig, path.resolve(__dirname, '../src/configs/all.json')); -console.log('--------------------- recommended.json ---------------------'); +console.log(); +console.log( + '------------------------------ recommended.json ------------------------------', +); const recommendedConfig: LinterConfig = { extends: './configs/base.json', rules: ruleEntries diff --git a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts new file mode 100644 index 00000000000..be97971186c --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts @@ -0,0 +1,27 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import fs from 'fs'; +import path from 'path'; +import { logRule } from './log'; + +function checkForRuleDocs( + rules: Record>, +): boolean { + const ruleDocs = new Set( + fs.readdirSync(path.resolve(__dirname, '../../docs/rules')), + ); + + let hasErrors = false; + Object.keys(rules).forEach(ruleName => { + const ruleHasDoc = ruleDocs.has(`${ruleName}.md`); + hasErrors = hasErrors || !ruleHasDoc; + logRule( + ruleHasDoc, + ruleName, + `Couldn't find file docs/rules/${ruleName}.md`, + ); + }); + + return hasErrors; +} + +export { checkForRuleDocs }; diff --git a/packages/eslint-plugin/tools/validate-docs/index.ts b/packages/eslint-plugin/tools/validate-docs/index.ts new file mode 100644 index 00000000000..6273ff61c87 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/index.ts @@ -0,0 +1,34 @@ +import chalk from 'chalk'; +import plugin from '../../src/index'; +import { checkForRuleDocs } from './check-for-rule-docs'; +import { parseReadme } from './parse-readme'; +import { validateTableStructure } from './validate-table-structure'; +import { validateTableRules } from './validate-table-rules'; + +const { rules } = plugin; + +let hasErrors = false; +console.log(chalk.underline('Checking for rule docs')); +hasErrors = hasErrors || checkForRuleDocs(rules); + +console.log(); +console.log(chalk.underline('Valdiating README.md')); +const rulesTable = parseReadme(); + +console.log(); +console.log(chalk.italic('Checking table structure...')); +hasErrors = hasErrors || validateTableStructure(rules, rulesTable); + +console.log(); +console.log(chalk.italic('Checking rules...')); +hasErrors = hasErrors || validateTableRules(rules, rulesTable); + +if (hasErrors) { + console.log('\n\n'); + console.error( + chalk.bold.bgRed.white('There were errors found in the documentation.'), + ); + console.log('\n\n'); + // eslint-disable-next-line no-process-exit + process.exit(1); +} diff --git a/packages/eslint-plugin/tools/validate-docs/log.ts b/packages/eslint-plugin/tools/validate-docs/log.ts new file mode 100644 index 00000000000..b00ce09c500 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/log.ts @@ -0,0 +1,20 @@ +import chalk from 'chalk'; + +function logRule( + success: boolean, + ruleName: string, + ...messages: string[] +): void { + if (success) { + console.log(chalk.bold.green('✔'), chalk.dim(ruleName)); + } else { + logError(chalk.bold(ruleName)); + messages.forEach(m => console.error(chalk.bold.red(' -'), m)); + } +} + +function logError(...messages: string[]): void { + console.error(chalk.bold.red('✗'), ...messages); +} + +export { logError, logRule }; diff --git a/packages/eslint-plugin/tools/validate-docs/parse-readme.ts b/packages/eslint-plugin/tools/validate-docs/parse-readme.ts new file mode 100644 index 00000000000..92afaa37dd2 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/parse-readme.ts @@ -0,0 +1,29 @@ +import fs from 'fs'; +import marked from 'marked'; +import path from 'path'; + +function parseReadme(): marked.Tokens.Table { + const readmeRaw = fs.readFileSync( + path.resolve(__dirname, '../../README.md'), + 'utf8', + ); + const readme = marked.lexer(readmeRaw, { + gfm: true, + tables: true, + silent: false, + }); + + // find the table + const rulesTable = readme.find( + token => token.type === 'table', + ) as marked.Tokens.Table; + if (!rulesTable) { + console.error('Could not find the rules table in README.md'); + // eslint-disable-next-line no-process-exit + process.exit(1); + } + + return rulesTable; +} + +export { parseReadme }; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts new file mode 100644 index 00000000000..98dd40437d9 --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts @@ -0,0 +1,123 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import chalk from 'chalk'; +import fs from 'fs'; +import marked from 'marked'; +import path from 'path'; +import { logRule } from './log'; + +function validateTableRules( + rules: Record>, + rulesTable: marked.Tokens.Table, +): boolean { + let hasErrors = false; + + Object.entries(rules).forEach(([ruleName, rule]) => { + const row = rulesTable.cells.find(row => + row[0].includes(`/${ruleName}.md`), + ); + + if (!row) { + if (!rule.meta.deprecated) { + hasErrors = true; + logRule(false, ruleName, 'Missing entry in table'); + return; + } + + // all is well, the rule shouldn't have a row as it's deprecated + return; + } + if (row && rule.meta.deprecated) { + hasErrors = true; + logRule( + false, + ruleName, + 'Rule is marked as deprecated, should not have an entry in the table', + ); + return; + } + + const errors: string[] = []; + const [ + rowLink, + rowDescription, + rowIsRecommended, + rowIsFixable, + rowNeedsTypeInfo, + ] = row; + + function validateTableBoolean( + value: boolean, + cell: string, + trueString: string, + columnLabel: string, + ): void { + if (value && cell !== trueString) { + errors.push( + `Rule ${chalk.red( + 'not', + )} marked as ${columnLabel} when it ${chalk.bold('should')} be`, + ); + } + + if (!value && cell !== '') { + errors.push( + `Rule ${chalk.red( + 'was', + )} marked as ${columnLabel} when it ${chalk.bold('should not')} be`, + ); + } + } + + const expectedLink = `[\`@typescript-eslint/${ruleName}\`](./docs/rules/${ruleName}.md)`; + if (rowLink !== expectedLink) { + errors.push( + `Link is invalid.`, + ` Expected: ${chalk.underline(expectedLink)}`, + ` Received: ${chalk.underline(rowLink)}`, + ); + } + + const expectedDescription = rule.meta.docs.description; + if (rowDescription !== expectedDescription) { + errors.push( + 'Description does not match the rule metadata.', + ` Expected: ${chalk.underline(expectedDescription)}`, + ` Received: ${chalk.underline(rowDescription)}`, + ); + } + + validateTableBoolean( + !!rule.meta.docs.recommended, + rowIsRecommended, + ':heavy_check_mark:', + 'recommended', + ); + + validateTableBoolean( + rule.meta.fixable !== undefined, + rowIsFixable, + ':wrench:', + 'fixable', + ); + + // quick-and-dirty check to see if it uses parserServices + // not perfect but should be good enough + const ruleFileContents = fs.readFileSync( + path.resolve(__dirname, `../../src/rules/${ruleName}.ts`), + ); + const usesTypeInformation = ruleFileContents.includes('getParserServices'); + validateTableBoolean( + usesTypeInformation, + rowNeedsTypeInfo, + ':thought_balloon:', + 'requiring type information', + ); + + hasErrors = hasErrors || errors.length > 0; + logRule(errors.length === 0, ruleName, ...errors); + }); + + return hasErrors; +} + +export { validateTableRules }; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts new file mode 100644 index 00000000000..5c4cdbdb68b --- /dev/null +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts @@ -0,0 +1,48 @@ +import { TSESLint } from '@typescript-eslint/experimental-utils'; +import chalk from 'chalk'; +import marked from 'marked'; +import { logError } from './log'; + +function validateTableStructure( + rules: Record>, + rulesTable: marked.Tokens.Table, +): boolean { + const ruleNames = Object.keys(rules).sort(); + let hasErrors = false; + + rulesTable.cells.forEach((row, rowIndex) => { + const match = row[0].match(/\[`@typescript-eslint\/(.+)`\]/); + if (!match) { + logError(chalk.bold(`Unable to parse link in row ${rowIndex}:`), row[0]); + hasErrors = true; + return; + } + + const rowRuleName = match[1]; + const ruleIndex = ruleNames.findIndex(ruleName => rowRuleName === ruleName); + if (ruleIndex === -1) { + logError( + chalk.bold( + `Found rule ${rowRuleName} in table, but it doesn't exist in the plugin.`, + ), + ); + hasErrors = true; + return; + } + + if (ruleIndex !== rowIndex) { + console.error( + chalk.bold.red('✗'), + chalk.bold('Sorting:'), + 'Incorrect line number for', + chalk.bold(rowRuleName), + ); + hasErrors = true; + return; + } + }); + + return hasErrors; +} + +export { validateTableStructure }; diff --git a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts index b20e70af213..c1e381af72e 100644 --- a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts +++ b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts @@ -14,9 +14,7 @@ type RemoveProps< > = Pick>; // we'll automatically add the url + tslint description for people. -type CreateRuleMetaDocs = RemoveProps & { - tslintName?: string; -}; +type CreateRuleMetaDocs = RemoveProps; type CreateRuleMeta = { docs: CreateRuleMetaDocs; } & RemoveProps, 'docs'>; @@ -48,9 +46,6 @@ export function RuleCreator(urlCreator: (ruleName: string) => string) { docs: { ...meta.docs, url: urlCreator(name), - extraDescription: meta.docs.tslintName - ? [`\`${meta.docs.tslintName}\` from TSLint`] - : undefined, }, }, create(context) { diff --git a/yarn.lock b/yarn.lock index 4f4a0d81f56..12e5d2f322d 100644 --- a/yarn.lock +++ b/yarn.lock @@ -1249,6 +1249,11 @@ resolved "https://registry.yarnpkg.com/@types/lodash/-/lodash-4.14.123.tgz#39be5d211478c8dd3bdae98ee75bb7efe4abfe4d" integrity sha512-pQvPkc4Nltyx7G1Ww45OjVqUsJP4UsZm+GWJpigXgkikZqJgRm4c48g027o6tdgubWHwFRF15iFd+Y4Pmqv6+Q== +"@types/marked@^0.6.5": + version "0.6.5" + resolved "https://registry.yarnpkg.com/@types/marked/-/marked-0.6.5.tgz#3cf2a56ef615dad24aaf99784ef90a9eba4e29d8" + integrity sha512-6kBKf64aVfx93UJrcyEZ+OBM5nGv4RLsI6sR1Ar34bpgvGVRoyTgpxn4ZmtxOM5aDTAaaznYuYUH8bUX3Nk3YA== + "@types/minimatch@*": version "3.0.3" resolved "https://registry.yarnpkg.com/@types/minimatch/-/minimatch-3.0.3.tgz#3dca0e3f33b200fc7d1139c0cd96c1268cadfd9d" @@ -1400,11 +1405,6 @@ any-observable@^0.3.0: resolved "https://registry.yarnpkg.com/any-observable/-/any-observable-0.3.0.tgz#af933475e5806a67d0d7df090dd5e8bef65d119b" integrity sha512-/FQM1EDkTsf63Ub2C6O7GuYFDsSXUwsaZDurV0np41ocwq0jthUAYCmhBX9f+KwlaCgIuWyr/4WlUQUBfKfZog== -any-promise@^1.0.0: - version "1.3.0" - resolved "https://registry.yarnpkg.com/any-promise/-/any-promise-1.3.0.tgz#abc6afeedcea52e809cdc0376aed3ce39635d17f" - integrity sha1-q8av7tzqUugJzcA3au0845Y10X8= - anymatch@^2.0.0: version "2.0.0" resolved "https://registry.yarnpkg.com/anymatch/-/anymatch-2.0.0.tgz#bcb24b4f37934d9aa7ac17b4adaf89e7c76ef2eb" @@ -1930,11 +1930,6 @@ cli-cursor@^2.0.0, cli-cursor@^2.1.0: dependencies: restore-cursor "^2.0.0" -cli-spinners@^2.0.0: - version "2.1.0" - resolved "https://registry.yarnpkg.com/cli-spinners/-/cli-spinners-2.1.0.tgz#22c34b4d51f573240885b201efda4e4ec9fff3c7" - integrity sha512-8B00fJOEh1HPrx4fo5eW16XmE1PcL1tGpGrxy63CXGP9nHdPBN63X75hA1zhvQuhVztJWLqV58Roj2qlNM7cAA== - cli-truncate@^0.2.1: version "0.2.1" resolved "https://registry.yarnpkg.com/cli-truncate/-/cli-truncate-0.2.1.tgz#9f15cfbb0705005369216c626ac7d05ab90dd574" @@ -2505,7 +2500,7 @@ diff-sequences@^24.3.0: resolved "https://registry.yarnpkg.com/diff-sequences/-/diff-sequences-24.3.0.tgz#0f20e8a1df1abddaf4d9c226680952e64118b975" integrity sha512-xLqpez+Zj9GKSnPWS0WZw1igGocZ+uua8+y+5dDNTT934N3QuY1sp2LkHzwiaYQGz60hMq0pjAshdeXm5VUOEw== -diff@^3.1.0, diff@^3.2.0, diff@^3.5.0: +diff@^3.1.0, diff@^3.2.0: version "3.5.0" resolved "https://registry.yarnpkg.com/diff/-/diff-3.5.0.tgz#800c0dd1e0a8bfbc95835c202ad220fe317e5a12" integrity sha512-A46qtFgd+g7pDZinpnwiRJtxbC1hpgf0uzP3iG89scHk0AUC7A1TGxf5OiiOUv/JMZR8GOt8hL900hV0bOy5xA== @@ -2655,18 +2650,6 @@ escodegen@^1.9.1: optionalDependencies: source-map "~0.6.1" -eslint-docs@^0.2.6: - version "0.2.7" - resolved "https://registry.yarnpkg.com/eslint-docs/-/eslint-docs-0.2.7.tgz#f208c3420fa2613f215a8daf5b9d75e9e7aa29ea" - integrity sha512-ylCFv96SW3aaWBrMFA7gai5tYntFXjy25CWNZWlAvamKCl7OYCTUfdUI40eAkO+3taxhGhTwCnIMHnwWwBxeYw== - dependencies: - chalk "^2.4.1" - detect-newline "^2.1.0" - diff "^3.5.0" - mz "^2.7.0" - ora "^3.0.0" - read-pkg-up "^4.0.0" - eslint-plugin-eslint-plugin@^2.0.1: version "2.0.1" resolved "https://registry.yarnpkg.com/eslint-plugin-eslint-plugin/-/eslint-plugin-eslint-plugin-2.0.1.tgz#d275434969dbde3da1d4cb7a121dc8d88457c786" @@ -4928,6 +4911,11 @@ map-visit@^1.0.0: dependencies: object-visit "^1.0.0" +marked@^0.6.2: + version "0.6.2" + resolved "https://registry.yarnpkg.com/marked/-/marked-0.6.2.tgz#c574be8b545a8b48641456ca1dbe0e37b6dccc1a" + integrity sha512-LqxwVH3P/rqKX4EKGz7+c2G9r98WeM/SW34ybhgNGhUQNKtf1GmmSkJ6cDGJ/t6tiyae49qRkpyTw2B9HOrgUA== + matcher@^1.0.0: version "1.1.1" resolved "https://registry.yarnpkg.com/matcher/-/matcher-1.1.1.tgz#51d8301e138f840982b338b116bb0c09af62c1c2" @@ -5166,15 +5154,6 @@ mute-stream@~0.0.4: resolved "https://registry.yarnpkg.com/mute-stream/-/mute-stream-0.0.8.tgz#1630c42b2251ff81e2a283de96a5497ea92e5e0d" integrity sha512-nnbWWOkoWyUsTjKrhgD0dcz22mdkSnpYqbEjIm2nhwhuxlSkpywJmBo8h0ZqJdkp73mb90SssHkN4rsRaBAfAA== -mz@^2.7.0: - version "2.7.0" - resolved "https://registry.yarnpkg.com/mz/-/mz-2.7.0.tgz#95008057a56cafadc2bc63dde7f9ff6955948e32" - integrity sha512-z81GNO7nnYMEhrGh9LeymoE4+Yr0Wn5McHIZMK5cfQCl+NDX08sCZgUc9/6MHni9IWuFLm1Z3HTCXu2z9fN62Q== - dependencies: - any-promise "^1.0.0" - object-assign "^4.0.1" - thenify-all "^1.0.0" - nan@^2.12.1: version "2.13.2" resolved "https://registry.yarnpkg.com/nan/-/nan-2.13.2.tgz#f51dc7ae66ba7d5d55e1e6d4d8092e802c9aefe7" @@ -5521,18 +5500,6 @@ optionator@^0.8.1, optionator@^0.8.2: type-check "~0.3.2" wordwrap "~1.0.0" -ora@^3.0.0: - version "3.4.0" - resolved "https://registry.yarnpkg.com/ora/-/ora-3.4.0.tgz#bf0752491059a3ef3ed4c85097531de9fdbcd318" - integrity sha512-eNwHudNbO1folBP3JsZ19v9azXWtQZjICdr3Q0TDPIaeBQ3mXLrh54wM+er0+hSp+dWKf+Z8KM58CYzEyIYxYg== - dependencies: - chalk "^2.4.2" - cli-cursor "^2.1.0" - cli-spinners "^2.0.0" - log-symbols "^2.2.0" - strip-ansi "^5.2.0" - wcwidth "^1.0.1" - os-homedir@^1.0.0: version "1.0.2" resolved "https://registry.yarnpkg.com/os-homedir/-/os-homedir-1.0.2.tgz#ffbc4988336e0e833de0c168c7ef152121aa7fb3" @@ -6830,7 +6797,7 @@ strip-ansi@^4.0.0: dependencies: ansi-regex "^3.0.0" -strip-ansi@^5.0.0, strip-ansi@^5.1.0, strip-ansi@^5.2.0: +strip-ansi@^5.0.0, strip-ansi@^5.1.0: version "5.2.0" resolved "https://registry.yarnpkg.com/strip-ansi/-/strip-ansi-5.2.0.tgz#8c9a536feb6afc962bdfa5b104a5091c1ad9c0ae" integrity sha512-DuRs1gKbBqsMKIZlrffwlug8MHkcnpjs5VPmL1PAh+mA30U0DTotfDZ0d2UUsXpPmPmMMJ6W773MaA3J+lbiWA== @@ -6978,20 +6945,6 @@ text-table@^0.2.0: resolved "https://registry.yarnpkg.com/text-table/-/text-table-0.2.0.tgz#7f5ee823ae805207c00af2df4a84ec3fcfa570b4" integrity sha1-f17oI66AUgfACvLfSoTsP8+lcLQ= -thenify-all@^1.0.0: - version "1.6.0" - resolved "https://registry.yarnpkg.com/thenify-all/-/thenify-all-1.6.0.tgz#1a1918d402d8fc3f98fbf234db0bcc8cc10e9726" - integrity sha1-GhkY1ALY/D+Y+/I02wvMjMEOlyY= - dependencies: - thenify ">= 3.1.0 < 4" - -"thenify@>= 3.1.0 < 4": - version "3.3.0" - resolved "https://registry.yarnpkg.com/thenify/-/thenify-3.3.0.tgz#e69e38a1babe969b0108207978b9f62b88604839" - integrity sha1-5p44obq+lpsBCCB5eLn2K4hgSDk= - dependencies: - any-promise "^1.0.0" - throat@^4.0.0: version "4.1.0" resolved "https://registry.yarnpkg.com/throat/-/throat-4.1.0.tgz#89037cbc92c56ab18926e6ba4cbb200e15672a6a" @@ -7335,7 +7288,7 @@ walker@^1.0.7, walker@~1.0.5: dependencies: makeerror "1.0.x" -wcwidth@^1.0.0, wcwidth@^1.0.1: +wcwidth@^1.0.0: version "1.0.1" resolved "https://registry.yarnpkg.com/wcwidth/-/wcwidth-1.0.1.tgz#f0b0dcf915bc5ff1528afadb2c0e17b532da2fe8" integrity sha1-8LDc+RW8X/FSivrbLA4XtTLaL+g= From 5792aa1e45b7643e58f3187879c431ddd5b432f6 Mon Sep 17 00:00:00 2001 From: Anders Kaseorg Date: Fri, 17 May 2019 09:19:50 -0700 Subject: [PATCH 25/41] chore: Add repository.directory to all package.json --- packages/eslint-plugin-tslint/package.json | 6 +++++- packages/eslint-plugin/package.json | 6 +++++- packages/experimental-utils/package.json | 6 +++++- packages/parser/package.json | 6 +++++- packages/typescript-estree/package.json | 6 +++++- 5 files changed, 25 insertions(+), 5 deletions(-) diff --git a/packages/eslint-plugin-tslint/package.json b/packages/eslint-plugin-tslint/package.json index fba222f6dbf..02d6c309f6c 100644 --- a/packages/eslint-plugin-tslint/package.json +++ b/packages/eslint-plugin-tslint/package.json @@ -13,7 +13,11 @@ "engines": { "node": "^6.14.0 || ^8.10.0 || >=9.10.0" }, - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/eslint-plugin-tslint" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index bbefaa09b00..0165589f6de 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -18,7 +18,11 @@ "README.md", "LICENSE" ], - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/eslint-plugin" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index 9d51a32c4fd..ff0d99bf7df 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -16,7 +16,11 @@ "README.md", "LICENSE" ], - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/experimental-utils" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, diff --git a/packages/parser/package.json b/packages/parser/package.json index d33fa890f50..50dcf49b907 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -11,7 +11,11 @@ "engines": { "node": "^6.14.0 || ^8.10.0 || >=9.10.0" }, - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/parser" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 65b6b41440f..400d1820481 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -12,7 +12,11 @@ "engines": { "node": ">=6.14.0" }, - "repository": "typescript-eslint/typescript-eslint", + "repository": { + "type": "git", + "url": "https://github.com/typescript-eslint/typescript-eslint.git", + "directory": "packages/typescript-estree" + }, "bugs": { "url": "https://github.com/typescript-eslint/typescript-eslint/issues" }, From 6cf4b9f364c44bccef84128eb19ad5cd74155475 Mon Sep 17 00:00:00 2001 From: Amir Nissim Date: Wed, 22 May 2019 20:02:52 +0300 Subject: [PATCH 26/41] docs(eslint-plugin): Add note for running eslint with ext for .ts files (#547) --- packages/eslint-plugin/README.md | 2 ++ 1 file changed, 2 insertions(+) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 18dc3a6e6cd..6b605d31b57 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -68,6 +68,8 @@ If you want to use rules which require type information, you will need to specif See [@typescript-eslint/parser's README.md](../parser/README.md) for more information on the available "parserOptions". +**Note: Make sure to use `eslint --ext .js,.ts` since by [default](https://eslint.org/docs/user-guide/command-line-interface#--ext) `eslint` will only search for .js files.** + ## Usage with Prettier Install [`eslint-config-prettier`](https://github.com/prettier/eslint-config-prettier) to disable our code formatting related rules: From cca1714c4bcb7a0349b4e10103870cc46bedf052 Mon Sep 17 00:00:00 2001 From: Scott O'Hara Date: Fri, 24 May 2019 11:48:52 +1000 Subject: [PATCH 27/41] docs(eslint-plugin): [no-extraneous-class] fix option keys (#551) --- packages/eslint-plugin/docs/rules/no-extraneous-class.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/no-extraneous-class.md b/packages/eslint-plugin/docs/rules/no-extraneous-class.md index 0fe1f691e20..882d7e26a55 100644 --- a/packages/eslint-plugin/docs/rules/no-extraneous-class.md +++ b/packages/eslint-plugin/docs/rules/no-extraneous-class.md @@ -50,9 +50,9 @@ const StaticOnly = { This rule accepts a single object option. -- `constructorOnly: true` will silence warnings about classes containing only a constructor. +- `allowConstructorOnly: true` will silence warnings about classes containing only a constructor. - `allowEmpty: true` will silence warnings about empty classes. -- `staticOnly: true` will silence warnings about classes containing only static members. +- `allowStaticOnly: true` will silence warnings about classes containing only static members. ## When Not To Use It From bba42eb6179c98d98646039a49df4395e9e160c7 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?Bence=20D=C3=A1nyi?= Date: Tue, 28 May 2019 18:48:17 +0200 Subject: [PATCH 28/41] test(eslint-plugin): add regression tests for invalid async modifiers (#560) --- .../rules/promise-function-async.test.ts | 23 +++++++++++++++++++ 1 file changed, 23 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts index 3910cea5604..13b4a18c2e8 100644 --- a/packages/eslint-plugin/tests/rules/promise-function-async.test.ts +++ b/packages/eslint-plugin/tests/rules/promise-function-async.test.ts @@ -42,6 +42,29 @@ class Test { public async asyncPromiseMethodB() { return new Promise(); } +} + `, + ` +class InvalidAsyncModifiers { + public constructor() { + return new Promise(); + } + public get asyncGetter() { + return new Promise(); + } + public set asyncGetter(p: Promise) { + return p; + } +} + `, + ` +const invalidAsyncModifiers = { + get asyncGetter() { + return new Promise(); + }, + set asyncGetter(p: Promise) { + return p; + } } `, // https://github.com/typescript-eslint/typescript-eslint/issues/227 From e6b2eff79beec82e36c3e7436c9519aa3741df75 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Tue, 28 May 2019 09:50:02 -0700 Subject: [PATCH 29/41] docs(experimental-utils): fix table links (#545) --- packages/experimental-utils/README.md | 33 +++++++++++++-------------- 1 file changed, 16 insertions(+), 17 deletions(-) diff --git a/packages/experimental-utils/README.md b/packages/experimental-utils/README.md index 45ecc1a64f8..0dd9299b2b9 100644 --- a/packages/experimental-utils/README.md +++ b/packages/experimental-utils/README.md @@ -14,20 +14,19 @@ Once it is stable, it will be renamed to `@typescript-eslint/util` for a `2.0.0` ## Exports -| Name | Description | -| --------------------------- | ---------------------------------------------------------------------------------------------- | -| [`TSESTree`] | Types for the TypeScript flavour of ESTree created by `@typescript-eslint/typescript-estree`. | -| [`AST_NODE_TYPES`] | An enum with the names of every single _node_ found in `TSESTree`. | -| [`AST_TOKEN_TYPES`] | An enum with the names of every single _token_ found in `TSESTree`. | -| [`TSESLint`] | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | -| [`ESLintUtils`] | Tools for creating eslint rules with TypeScript. | -| [`ESLintUtils.RuleCreator`] | A function for creating strictly typed eslint rules with TypeScript. | -| [`ParserServices`] | The parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | - -[`AST_NODE_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) -[`AST_TOKEN_TYPES`](../packages/typescript-estree/src/ts-estree/ast-node-types.ts) -[`ESLintUtils`](./src/eslint-utils) -[`ESLintUtils.createRule`](./src/eslint-utils/createRule.ts) -[`ParserServices`](../packages/typescript-estree/src/ts-estree/parser.ts) -[`TSESTree`](../packages/typescript-estree/src/ts-estree/ts-estree.ts) -[`TSESLint`](./src/ts-eslint) +| Name | Description | +| ------------------- | ---------------------------------------------------------------------------------------------- | +| [`TSESTree`] | Types for the TypeScript flavour of ESTree created by `@typescript-eslint/typescript-estree`. | +| [`AST_NODE_TYPES`] | An enum with the names of every single _node_ found in `TSESTree`. | +| [`AST_TOKEN_TYPES`] | An enum with the names of every single _token_ found in `TSESTree`. | +| [`TSESLint`] | Types for ESLint, correctly typed to work with the types found in `TSESTree`. | +| [`ESLintUtils`] | Tools for creating eslint rules with TypeScript. | +| [`ParserServices`] | The parser services provided when parsing a file using `@typescript-eslint/typescript-estree`. | + +[`ast_node_types`]: ../packages/typescript-estree/src/ts-estree/ast-node-types.ts +[`ast_token_types`]: ../packages/typescript-estree/src/ts-estree/ast-node-types.ts +[`eslintutils`]: ./src/eslint-utils +[`eslintutils.createrule`]: ./src/eslint-utils/createRule.ts +[`parserservices`]: ../packages/typescript-estree/src/ts-estree/parser.ts +[`tsestree`]: ../packages/typescript-estree/src/ts-estree/ts-estree.ts +[`tseslint`]: ./src/ts-eslint From 8a0a0dd9f5120c39f0a1d76f238b02737b431079 Mon Sep 17 00:00:00 2001 From: bluelovers Date: Wed, 29 May 2019 13:08:08 +0800 Subject: [PATCH 30/41] docs(eslint-plugin): [explicit-member-accessibility] correct typo (#570) --- .../eslint-plugin/docs/rules/explicit-member-accessibility.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md index e410fa69b03..3ed9bb7e5d0 100644 --- a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md +++ b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md @@ -154,7 +154,7 @@ There are three ways in which an override can be used. #### Disallow the use of public on a given member -e.g. `[ { overrides: { constructor: 'no-public' } } ]` +e.g. `[ { overrides: { constructors: 'no-public' } } ]` The following patterns are considered incorrect with the example override From f354a3dffd413471c19ed10d5a35f9338643bb12 Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" Date: Fri, 31 May 2019 11:10:04 -0400 Subject: [PATCH 31/41] chore(deps): bump fstream from 1.0.11 to 1.0.12 (#578) --- yarn.lock | 20 ++++++++++++++++---- 1 file changed, 16 insertions(+), 4 deletions(-) diff --git a/yarn.lock b/yarn.lock index 12e5d2f322d..25bfac9a586 100644 --- a/yarn.lock +++ b/yarn.lock @@ -3079,9 +3079,9 @@ fsevents@^1.2.7: node-pre-gyp "^0.12.0" fstream@^1.0.0, fstream@^1.0.2: - version "1.0.11" - resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.11.tgz#5c1fb1f117477114f0632a0eb4b71b3cb0fd3171" - integrity sha1-XB+x8RdHcRTwYyoOtLcbPLD9MXE= + version "1.0.12" + resolved "https://registry.yarnpkg.com/fstream/-/fstream-1.0.12.tgz#4e8ba8ee2d48be4f7d0de505455548eae5932045" + integrity sha512-WvJ193OHa0GHPEL+AycEJgxvBEwyfRkN1vhjca23OaPVMCaLCXTd5qAu82AjTcgP1UJmytkOKb63Ypde7raDIg== dependencies: graceful-fs "^4.1.2" inherits "~2.0.0" @@ -3281,7 +3281,7 @@ glob@7.1.2: once "^1.3.0" path-is-absolute "^1.0.0" -glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: +glob@^7.0.3, glob@^7.1.1, glob@^7.1.2: version "7.1.3" resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.3.tgz#3960832d3f1574108342dafd3a67b332c0969df1" integrity sha512-vcfuiIxogLV4DlGBHIUOwI0IbrJ8HWPc4MU7HzviGeNho/UJDfi6B5p3sHeWIQ0KGIU0Jpxi5ZHxemQfLkkAwQ== @@ -3293,6 +3293,18 @@ glob@^7.0.3, glob@^7.1.1, glob@^7.1.2, glob@^7.1.3: once "^1.3.0" path-is-absolute "^1.0.0" +glob@^7.1.3: + version "7.1.4" + resolved "https://registry.yarnpkg.com/glob/-/glob-7.1.4.tgz#aa608a2f6c577ad357e1ae5a5c26d9a8d1969255" + integrity sha512-hkLPepehmnKk41pUGm3sYxoFs/umurYfYJCerbXEyFIWcAzvpipAgVkBqqT9RBKMGjnq6kMuyYwha6csxbiM1A== + dependencies: + fs.realpath "^1.0.0" + inflight "^1.0.4" + inherits "2" + minimatch "^3.0.4" + once "^1.3.0" + path-is-absolute "^1.0.0" + global-dirs@^0.1.0: version "0.1.1" resolved "https://registry.yarnpkg.com/global-dirs/-/global-dirs-0.1.1.tgz#b319c0dd4607f353f3be9cca4c72fc148c49f445" From 50a493e4c98f06b768aa9234c29e9da2630b2fc4 Mon Sep 17 00:00:00 2001 From: Torleif Berger Date: Mon, 3 Jun 2019 15:42:24 +0200 Subject: [PATCH 32/41] feat(eslint-plugin): [explicit-function-return-type] allowHigherOrderFunctions (#193) (#538) --- .../rules/explicit-function-return-type.md | 33 +++- .../rules/explicit-function-return-type.ts | 59 +++++- .../explicit-function-return-type.test.ts | 186 ++++++++++++++++++ 3 files changed, 275 insertions(+), 3 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md index 2812023ffba..a20aa83c7a5 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -65,13 +65,16 @@ The rule accepts an options object with the following properties: type Options = { // if true, only functions which are part of a declaration will be checked allowExpressions?: boolean; - // if true, type annotations are also allowed on the variable of a function expression rather than on the function directly. + // if true, type annotations are also allowed on the variable of a function expression rather than on the function directly allowTypedFunctionExpressions?: boolean; + // if true, functions immediately returning another function expression will not be checked + allowHigherOrderFunctions?: boolean; }; const defaults = { allowExpressions: false, allowTypedFunctionExpressions: false, + allowHigherOrderFunctions: false, }; ``` @@ -121,7 +124,7 @@ let funcExpr: FuncType = function() { }; let asTyped = (() => '') as () => string; -let caasTyped = <() => string>(() => ''); +let castTyped = <() => string>(() => ''); interface ObjectType { foo(): number; @@ -137,6 +140,32 @@ let objectPropCast = { }; ``` +### allowHigherOrderFunctions + +Examples of **incorrect** code for this rule with `{ allowHigherOrderFunctions: true }`: + +```ts +var arrowFn = (x: number) => (y: number) => x + y; + +function fn(x: number) { + return function(y: number) { + return x + y; + }; +} +``` + +Examples of **correct** code for this rule with `{ allowHigherOrderFunctions: true }`: + +```ts +var arrowFn = (x: number) => (y: number): number => x + y; + +function fn(x: number) { + return function(y: number): number { + return x + y; + }; +} +``` + ## When Not To Use It If you don't wish to prevent calling code from using function return values in unexpected ways, then diff --git a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts index be6bb14c448..876f42517d6 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -8,6 +8,7 @@ type Options = [ { allowExpressions?: boolean; allowTypedFunctionExpressions?: boolean; + allowHigherOrderFunctions?: boolean; } ]; type MessageIds = 'missingReturnType'; @@ -35,6 +36,9 @@ export default util.createRule({ allowTypedFunctionExpressions: { type: 'boolean', }, + allowHigherOrderFunctions: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -44,6 +48,7 @@ export default util.createRule({ { allowExpressions: false, allowTypedFunctionExpressions: false, + allowHigherOrderFunctions: false, }, ], create(context, [options]) { @@ -138,6 +143,50 @@ export default util.createRule({ ); } + /** + * Checks if a function belongs to: + * `() => () => ...` + * `() => function () { ... }` + * `() => { return () => ... }` + * `() => { return function () { ... } }` + * `function fn() { return () => ... }` + * `function fn() { return function() { ... } }` + */ + function doesImmediatelyReturnFunctionExpression({ + body, + }: + | TSESTree.ArrowFunctionExpression + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression): boolean { + // Should always have a body; really checking just in case + /* istanbul ignore if */ if (!body) { + return false; + } + + // Check if body is a block with a single statement + if ( + body.type === AST_NODE_TYPES.BlockStatement && + body.body.length === 1 + ) { + const [statement] = body.body; + + // Check if that statement is a return statement with an argument + if ( + statement.type === AST_NODE_TYPES.ReturnStatement && + !!statement.argument + ) { + // If so, check that returned argument as body + body = statement.argument; + } + } + + // Check if the body being returned is a function expression + return ( + body.type === AST_NODE_TYPES.ArrowFunctionExpression || + body.type === AST_NODE_TYPES.FunctionExpression + ); + } + /** * Checks if a function declaration/expression has a return type. */ @@ -147,6 +196,13 @@ export default util.createRule({ | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, ): void { + if ( + options.allowHigherOrderFunctions && + doesImmediatelyReturnFunctionExpression(node) + ) { + return; + } + if ( node.returnType || isConstructor(node.parent) || @@ -169,7 +225,8 @@ export default util.createRule({ function checkFunctionExpressionReturnType( node: TSESTree.ArrowFunctionExpression | TSESTree.FunctionExpression, ): void { - if (node.parent) { + // Should always have a parent; checking just in case + /* istanbul ignore else */ if (node.parent) { if (options.allowTypedFunctionExpressions) { if ( isTypeCast(node.parent) || diff --git a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts index 936b300134b..1b9209b7abf 100644 --- a/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-function-return-type.test.ts @@ -180,6 +180,71 @@ const myObj = { }; `, }, + { + filename: 'test.ts', + code: ` +() => (): void => {}; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => function (): void {}; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => { return (): void => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => { return function (): void {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +function fn() { return (): void => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +function fn() { return function (): void {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +function FunctionDeclaration() { + return function FunctionExpression_Within_FunctionDeclaration() { + return function FunctionExpression_Within_FunctionExpression() { + return () => { // ArrowFunctionExpression_Within_FunctionExpression + return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression + (): number => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + } + } + } +} + `, + options: [{ allowHigherOrderFunctions: true }], + }, + { + filename: 'test.ts', + code: ` +() => () => { return (): void => { return; } }; + `, + options: [{ allowHigherOrderFunctions: true }], + }, ], invalid: [ { @@ -364,5 +429,126 @@ const x: Foo = { }, ], }, + { + filename: 'test.ts', + code: ` +() => () => {}; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 7, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => function () {}; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 7, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => { return () => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 16, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => { return function () {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 16, + }, + ], + }, + { + filename: 'test.ts', + code: ` +function fn() { return () => {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 24, + }, + ], + }, + { + filename: 'test.ts', + code: ` +function fn() { return function () {} }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 24, + }, + ], + }, + { + filename: 'test.ts', + code: ` +function FunctionDeclaration() { + return function FunctionExpression_Within_FunctionDeclaration() { + return function FunctionExpression_Within_FunctionExpression() { + return () => { // ArrowFunctionExpression_Within_FunctionExpression + return () => // ArrowFunctionExpression_Within_ArrowFunctionExpression + () => 1 // ArrowFunctionExpression_Within_ArrowFunctionExpression_WithNoBody + } + } + } +} + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 7, + column: 11, + }, + ], + }, + { + filename: 'test.ts', + code: ` +() => () => { return () => { return; } }; + `, + options: [{ allowHigherOrderFunctions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 22, + }, + ], + }, ], }); From fac5c7d9fb24156883ffa23450456f6dc859ef9f Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 3 Jun 2019 08:38:48 -0700 Subject: [PATCH 33/41] fix(experimental-utils): Avoid typescript import at runtime (#584) See: https://github.com/typescript-eslint/typescript-eslint/pull/425#issuecomment-498162293 --- packages/experimental-utils/src/index.ts | 10 ++++++++-- 1 file changed, 8 insertions(+), 2 deletions(-) diff --git a/packages/experimental-utils/src/index.ts b/packages/experimental-utils/src/index.ts index 93b3831817c..137aa956cfc 100644 --- a/packages/experimental-utils/src/index.ts +++ b/packages/experimental-utils/src/index.ts @@ -6,9 +6,15 @@ export { ESLintUtils, TSESLint, TSESLintScope }; // for convenience's sake - export the types directly from here so consumers // don't need to reference/install both packages in their code + +// NOTE - this uses hard links inside ts-estree to avoid initing the entire package +// via its main file (which imports typescript at runtime). +// Not every eslint-plugin written in typescript requires typescript at runtime. export { AST_NODE_TYPES, AST_TOKEN_TYPES, - ParserServices, TSESTree, -} from '@typescript-eslint/typescript-estree'; +} from '@typescript-eslint/typescript-estree/dist/ts-estree'; +export { + ParserServices, +} from '@typescript-eslint/typescript-estree/dist/parser-options'; From b5e95edbb811ba239d2856e9caee21e802fbd8fb Mon Sep 17 00:00:00 2001 From: Jonathan Romano Date: Mon, 3 Jun 2019 18:20:26 -0400 Subject: [PATCH 34/41] docs: correct eslint-plugin issue template (#587) --- .github/ISSUE_TEMPLATE/eslint-plugin-typescript.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md index 1018a4ea7ab..010b5c01cb3 100644 --- a/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md +++ b/.github/ISSUE_TEMPLATE/eslint-plugin-typescript.md @@ -36,7 +36,7 @@ The more irrelevant code/config you give, the harder it is for us to investigate ```JSON { "rules": { - "typescript/": [""] + "@typescript-eslint/": [""] } } ``` From 83c1f5a3ab6dea451c35aeeda3034fe4bc906dd0 Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Wed, 5 Jun 2019 11:06:48 -0400 Subject: [PATCH 35/41] chore: removed lint and typecheck tasks from pre-push (#580) They're covered by the CI so save people some time and make it less likely for people to use `--no-verify` --- package.json | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/package.json b/package.json index a8ac88c3719..e74b25def8d 100644 --- a/package.json +++ b/package.json @@ -29,7 +29,7 @@ "lint": "eslint . --ext .js,.ts", "lint-fix": "eslint . --ext .js,.ts --fix", "pre-commit": "yarn lint-staged", - "pre-push": "yarn lint && yarn typecheck && yarn format-check", + "pre-push": "yarn format-check", "postinstall": "lerna bootstrap && yarn build && lerna link", "test": "lerna run test --parallel", "typecheck": "lerna run typecheck" From 861844d727a2ba7f75cdbba2571498078aee2671 Mon Sep 17 00:00:00 2001 From: Ian MacLeod Date: Wed, 5 Jun 2019 17:30:23 -0700 Subject: [PATCH 36/41] fix(typescript-estree): allow expressions in ExportDefaultDeclaration (#593) --- packages/typescript-estree/src/ts-estree/ts-estree.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index 504280e4850..e786e83e0df 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -659,7 +659,7 @@ export interface ExportAllDeclaration extends BaseNode { export interface ExportDefaultDeclaration extends BaseNode { type: AST_NODE_TYPES.ExportDefaultDeclaration; - declaration: ExportDeclaration; + declaration: ExportDeclaration | Expression; } export interface ExportNamedDeclaration extends BaseNode { From c03b6ed7a97c4d7aa836f61d3ac4affd58e6c360 Mon Sep 17 00:00:00 2001 From: Borek Bernard Date: Thu, 6 Jun 2019 17:30:57 +0200 Subject: [PATCH 37/41] docs(eslint-plugin): Add eslint:recommended to a usage example (#591) --- packages/eslint-plugin/README.md | 14 +++++++++++++- 1 file changed, 13 insertions(+), 1 deletion(-) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 6b605d31b57..87e8cfd72ee 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -43,7 +43,7 @@ Then configure the rules you want to use under the rules section. } ``` -You can also enable all the recommended rules at once. Add `plugin:@typescript-eslint/recommended` in extends: +You can also enable all the recommended rules for our plugin. Add `plugin:@typescript-eslint/recommended` in extends: ```json { @@ -51,6 +51,18 @@ You can also enable all the recommended rules at once. Add `plugin:@typescript-e } ``` +You can also use [eslint:recommended](https://eslint.org/docs/rules/) with this plugin. Add both `eslint:recommended` and `plugin:@typescript-eslint/eslint-recommended`: + +```json +{ + "extends": [ + "eslint:recommended", + "plugin:@typescript-eslint/eslint-recommended", + "plugin:@typescript-eslint/recommended" + ] +} +``` + If you want to use rules which require type information, you will need to specify a path to your tsconfig.json file in the "project" property of "parserOptions". ```json From 5d2b9626d86ca0ef14c2a45c233f096f356bccbd Mon Sep 17 00:00:00 2001 From: Damian Pieczynski Date: Fri, 7 Jun 2019 02:44:12 +0200 Subject: [PATCH 38/41] feat(*): support TypeScript versions >=3.2.1 <3.6.0 (#597) --- README.md | 2 +- package.json | 2 +- .../src/rules/indent-new-do-not-use/index.ts | 2 +- packages/eslint-plugin/src/rules/semi.ts | 2 +- packages/eslint-plugin/tests/configs/all.test.ts | 10 +++------- .../tools/validate-docs/check-for-rule-docs.ts | 4 ++-- .../tools/validate-docs/validate-table-rules.ts | 4 ++-- .../tools/validate-docs/validate-table-structure.ts | 4 ++-- .../src/eslint-utils/RuleCreator.ts | 10 ++-------- .../experimental-utils/src/eslint-utils/deepMerge.ts | 11 ++++------- packages/typescript-estree/src/parser.ts | 2 +- .../tests/lib/__snapshots__/convert.ts.snap | 2 +- yarn.lock | 8 ++++---- 13 files changed, 25 insertions(+), 38 deletions(-) diff --git a/README.md b/README.md index cf4261d695b..d0efc0b4919 100644 --- a/README.md +++ b/README.md @@ -203,7 +203,7 @@ The `canary` (latest master) version is: We will always endeavor to support the latest stable version of TypeScript. Sometimes, but not always, changes in TypeScript will not require breaking changes in this project, and so we are able to support more than one version of TypeScript. -**The version range of TypeScript currently supported by this parser is `>=3.2.1 <3.5.0`.** +**The version range of TypeScript currently supported by this parser is `>=3.2.1 <3.6.0`.** This is reflected in the `devDependency` requirement within the package.json file, and it is what the tests will be run against. We have an open `peerDependency` requirement in order to allow for experimentation on newer/beta versions of TypeScript. diff --git a/package.json b/package.json index e74b25def8d..2f0d94d38bd 100644 --- a/package.json +++ b/package.json @@ -78,6 +78,6 @@ "ts-jest": "^24.0.0", "ts-node": "^8.0.1", "tslint": "^5.11.0", - "typescript": ">=3.2.1 <3.5.0" + "typescript": ">=3.2.1 <3.6.0" } } diff --git a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts index 1944fdf2bf5..14088c55378 100644 --- a/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts +++ b/packages/eslint-plugin/src/rules/indent-new-do-not-use/index.ts @@ -1588,7 +1588,7 @@ export default createRule({ ); // For each ignored node selector, set up a listener to collect it into the `ignoredNodes` set. - const ignoredNodes = new Set(); + const ignoredNodes = new Set(); /** * Ignores a node diff --git a/packages/eslint-plugin/src/rules/semi.ts b/packages/eslint-plugin/src/rules/semi.ts index 6e40651dd2e..8130088eb30 100644 --- a/packages/eslint-plugin/src/rules/semi.ts +++ b/packages/eslint-plugin/src/rules/semi.ts @@ -52,7 +52,7 @@ export default util.createRule({ AST_NODE_TYPES.TSImportEqualsDeclaration, AST_NODE_TYPES.TSTypeAliasDeclaration, ].reduce((acc, node) => { - acc[node] = checkForSemicolon; + acc[node as string] = checkForSemicolon; return acc; }, {}); diff --git a/packages/eslint-plugin/tests/configs/all.test.ts b/packages/eslint-plugin/tests/configs/all.test.ts index 425e3a1426a..871576f2543 100644 --- a/packages/eslint-plugin/tests/configs/all.test.ts +++ b/packages/eslint-plugin/tests/configs/all.test.ts @@ -1,10 +1,6 @@ import rules from '../../src/rules'; import allConfig from '../../src/configs/all.json'; -import { TSESLint } from '@typescript-eslint/experimental-utils'; -interface IndexRules { - [name: string]: TSESLint.RuleModule; -} interface JsonRules { [name: string]: string; } @@ -12,10 +8,10 @@ interface JsonRules { describe('all.json config', () => { const RULE_NAME_PREFIX = '@typescript-eslint/'; - const typedRules: IndexRules = rules; - const notDeprecatedRuleNames = Object.keys(typedRules).reduce( + const rulesNames = Object.keys(rules) as (keyof typeof rules)[]; + const notDeprecatedRuleNames = rulesNames.reduce( (collection, name) => { - if (!typedRules[name].meta.deprecated) { + if (!rules[name].meta.deprecated) { collection.push(`${RULE_NAME_PREFIX}${name}`); } return collection; diff --git a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts index be97971186c..6012d46a3f1 100644 --- a/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts +++ b/packages/eslint-plugin/tools/validate-docs/check-for-rule-docs.ts @@ -3,8 +3,8 @@ import fs from 'fs'; import path from 'path'; import { logRule } from './log'; -function checkForRuleDocs( - rules: Record>, +function checkForRuleDocs( + rules: Record>, ): boolean { const ruleDocs = new Set( fs.readdirSync(path.resolve(__dirname, '../../docs/rules')), diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts index 98dd40437d9..5268bc67556 100644 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-rules.ts @@ -5,8 +5,8 @@ import marked from 'marked'; import path from 'path'; import { logRule } from './log'; -function validateTableRules( - rules: Record>, +function validateTableRules( + rules: Record>, rulesTable: marked.Tokens.Table, ): boolean { let hasErrors = false; diff --git a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts index 5c4cdbdb68b..4b5bb4151f4 100644 --- a/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts +++ b/packages/eslint-plugin/tools/validate-docs/validate-table-structure.ts @@ -3,8 +3,8 @@ import chalk from 'chalk'; import marked from 'marked'; import { logError } from './log'; -function validateTableStructure( - rules: Record>, +function validateTableStructure( + rules: Record>, rulesTable: marked.Tokens.Table, ): boolean { const ruleNames = Object.keys(rules).sort(); diff --git a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts index c1e381af72e..28b00b7ed9c 100644 --- a/packages/experimental-utils/src/eslint-utils/RuleCreator.ts +++ b/packages/experimental-utils/src/eslint-utils/RuleCreator.ts @@ -7,17 +7,11 @@ import { } from '../ts-eslint/Rule'; import { applyDefault } from './applyDefault'; -// Utility type to remove a list of properties from an object -type RemoveProps< - TObj extends Record, - TKeys extends keyof TObj -> = Pick>; - // we'll automatically add the url + tslint description for people. -type CreateRuleMetaDocs = RemoveProps; +type CreateRuleMetaDocs = Omit; type CreateRuleMeta = { docs: CreateRuleMetaDocs; -} & RemoveProps, 'docs'>; +} & Omit, 'docs'>; export function RuleCreator(urlCreator: (ruleName: string) => string) { // This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 diff --git a/packages/experimental-utils/src/eslint-utils/deepMerge.ts b/packages/experimental-utils/src/eslint-utils/deepMerge.ts index 3ae3518e9f2..0685fa9b0f8 100644 --- a/packages/experimental-utils/src/eslint-utils/deepMerge.ts +++ b/packages/experimental-utils/src/eslint-utils/deepMerge.ts @@ -1,4 +1,4 @@ -export type ObjectLike = Record; +type ObjectLike = Record; /** * Check if the variable contains an object stricly rejecting arrays @@ -16,14 +16,11 @@ export function isObjectNotArray(obj: T | any[]): obj is T { * @param second The second object * @returns a new object */ -export function deepMerge( - first: ObjectLike = {}, - second: ObjectLike = {}, -): T { +export function deepMerge(first: ObjectLike = {}, second: ObjectLike = {}) { // get the unique set of keys across both objects const keys = new Set(Object.keys(first).concat(Object.keys(second))); - return Array.from(keys).reduce( + return Array.from(keys).reduce( (acc, key) => { const firstHasKey = key in first; const secondHasKey = key in second; @@ -44,6 +41,6 @@ export function deepMerge( return acc; }, - {} as T, + {} as ObjectLike, ); } diff --git a/packages/typescript-estree/src/parser.ts b/packages/typescript-estree/src/parser.ts index 1a963859d96..81825267fcb 100644 --- a/packages/typescript-estree/src/parser.ts +++ b/packages/typescript-estree/src/parser.ts @@ -15,7 +15,7 @@ import { * This needs to be kept in sync with the top-level README.md in the * typescript-eslint monorepo */ -const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.5.0'; +const SUPPORTED_TYPESCRIPT_VERSIONS = '>=3.2.1 <3.6.0'; const ACTIVE_TYPESCRIPT_VERSION = ts.version; const isRunningSupportedTypeScriptVersion = semver.satisfies( ACTIVE_TYPESCRIPT_VERSION, diff --git a/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap index edb333d7c83..354bdd90f27 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/convert.ts.snap @@ -34,7 +34,7 @@ Object { }, "isDeclarationFile": false, "languageVariant": 1, - "languageVersion": 7, + "languageVersion": 8, "libReferenceDirectives": Array [], "lineMap": Array [ null, diff --git a/yarn.lock b/yarn.lock index 25bfac9a586..53025c9063a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -7155,10 +7155,10 @@ typedarray@^0.0.6: resolved "https://registry.yarnpkg.com/typedarray/-/typedarray-0.0.6.tgz#867ac74e3864187b1d3d47d996a78ec5c8830777" integrity sha1-hnrHTjhkGHsdPUfZlqeOxciDB3c= -"typescript@>=3.2.1 <3.5.0": - version "3.4.5" - resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.4.5.tgz#2d2618d10bb566572b8d7aad5180d84257d70a99" - integrity sha512-YycBxUb49UUhdNMU5aJ7z5Ej2XGmaIBL0x34vZ82fn3hGvD+bgrMrVDpatgz2f7YxUMJxMkbWxJZeAvDxVe7Vw== +"typescript@>=3.2.1 <3.6.0": + version "3.5.1" + resolved "https://registry.yarnpkg.com/typescript/-/typescript-3.5.1.tgz#ba72a6a600b2158139c5dd8850f700e231464202" + integrity sha512-64HkdiRv1yYZsSe4xC1WVgamNigVYjlssIoaH2HcZF0+ijsk5YK2g0G34w9wJkze8+5ow4STd22AynfO6ZYYLw== uglify-js@^3.1.4: version "3.5.10" From 31d5bd4934105f918f7bf34cb6523dda30fb1e4d Mon Sep 17 00:00:00 2001 From: Lucas Duailibe Date: Fri, 7 Jun 2019 00:38:13 -0300 Subject: [PATCH 39/41] fix(typescript-estree): stop ignoring comments in JSX with generic (#596) --- .../tests/lib/__snapshots__/comments.ts.snap | 3726 +++++++++++++++++ .../jsx-generic-with-comment-in-tag.src.js | 25 + .../typescript-estree/src/convert-comments.ts | 2 + .../tests/lib/__snapshots__/comments.ts.snap | 3726 +++++++++++++++++ .../semantic-diagnostics-enabled.ts.snap | 2 + 5 files changed, 7481 insertions(+) create mode 100644 packages/shared-fixtures/fixtures/comments/jsx-generic-with-comment-in-tag.src.js diff --git a/packages/parser/tests/lib/__snapshots__/comments.ts.snap b/packages/parser/tests/lib/__snapshots__/comments.ts.snap index 39b28f15c60..b38806f8d63 100644 --- a/packages/parser/tests/lib/__snapshots__/comments.ts.snap +++ b/packages/parser/tests/lib/__snapshots__/comments.ts.snap @@ -3004,6 +3004,3732 @@ Object { } `; +exports[`Comments fixtures/jsx-generic-with-comment-in-tag.src 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "name": "comp", + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + }, + "init": Object { + "children": Array [ + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 58, + 70, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 24, + 58, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 34, + 42, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 24, + 70, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 113, + 125, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": "foo", + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 75, + 113, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 85, + 93, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 75, + 125, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 168, + 180, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": "bar", + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 130, + 168, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 140, + 148, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 130, + 180, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 227, + 239, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": "foo", + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": "bar", + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 185, + 227, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 195, + 203, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 185, + 239, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "raw": " + + ", + "type": "JSXText", + "value": " + + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "name": "Component", + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 287, + 299, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "name": "Component", + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 245, + 287, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 255, + 263, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 245, + 299, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "name": "Component", + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 356, + 368, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": "foo", + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "name": "Component", + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 304, + 356, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 314, + 322, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 304, + 368, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "name": "Component", + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 425, + 437, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": "foo", + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "name": "Component", + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 373, + 425, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 383, + 391, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 373, + 437, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "name": "Component", + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 504, + 516, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": "foo", + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": "bar", + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "name": "Component", + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 442, + 504, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 452, + 460, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 442, + 516, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "raw": " + ", + "type": "JSXText", + "value": " + ", + }, + ], + "closingFragment": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 522, + ], + "type": "JSXClosingFragment", + }, + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "openingFragment": Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 19, + ], + "type": "JSXOpeningFragment", + }, + "range": Array [ + 17, + 522, + ], + "type": "JSXFragment", + }, + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 524, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 525, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [ + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 3, + }, + "start": Object { + "column": 23, + "line": 3, + }, + }, + "range": Array [ + 43, + 57, + ], + "type": "Block", + "value": " comment1 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 4, + }, + "start": Object { + "column": 27, + "line": 4, + }, + }, + "range": Array [ + 98, + 112, + ], + "type": "Block", + "value": " comment2 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 5, + }, + "start": Object { + "column": 23, + "line": 5, + }, + }, + "range": Array [ + 149, + 163, + ], + "type": "Block", + "value": " comment3 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 6, + }, + "start": Object { + "column": 27, + "line": 6, + }, + }, + "range": Array [ + 208, + 222, + ], + "type": "Block", + "value": " comment4 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 9, + }, + "start": Object { + "column": 6, + "line": 9, + }, + }, + "range": Array [ + 270, + 281, + ], + "type": "Line", + "value": " comment5", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 13, + }, + "start": Object { + "column": 6, + "line": 13, + }, + }, + "range": Array [ + 339, + 350, + ], + "type": "Line", + "value": " comment6", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 16, + }, + "start": Object { + "column": 6, + "line": 16, + }, + }, + "range": Array [ + 398, + 409, + ], + "type": "Line", + "value": " comment7", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 21, + }, + "start": Object { + "column": 6, + "line": 21, + }, + }, + "range": Array [ + 477, + 488, + ], + "type": "Line", + "value": " comment8", + }, + ], + "loc": Object { + "end": Object { + "column": 0, + "line": 26, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 526, + ], + "sourceType": "module", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 5, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + "value": "comp", + }, + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 1, + }, + "start": Object { + "column": 11, + "line": 1, + }, + }, + "range": Array [ + 11, + 12, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 13, + "line": 1, + }, + }, + "range": Array [ + 13, + 14, + ], + "type": "Punctuator", + "value": "(", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 18, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 3, + "line": 2, + }, + }, + "range": Array [ + 18, + 19, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "range": Array [ + 24, + 25, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "range": Array [ + 34, + 35, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 21, + "line": 3, + }, + }, + "range": Array [ + 41, + 42, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 37, + "line": 3, + }, + }, + "range": Array [ + 57, + 58, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 39, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "range": Array [ + 58, + 59, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 40, + "line": 3, + }, + "start": Object { + "column": 39, + "line": 3, + }, + }, + "range": Array [ + 59, + 60, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 49, + "line": 3, + }, + }, + "range": Array [ + 69, + 70, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "range": Array [ + 75, + 76, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "range": Array [ + 85, + 86, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 21, + "line": 4, + }, + }, + "range": Array [ + 92, + 93, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 41, + "line": 4, + }, + }, + "range": Array [ + 112, + 113, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "range": Array [ + 113, + 114, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 4, + }, + "start": Object { + "column": 43, + "line": 4, + }, + }, + "range": Array [ + 114, + 115, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 53, + "line": 4, + }, + }, + "range": Array [ + 124, + 125, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "range": Array [ + 130, + 131, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "range": Array [ + 140, + 141, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 21, + "line": 5, + }, + }, + "range": Array [ + 147, + 148, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 41, + "line": 5, + }, + }, + "range": Array [ + 167, + 168, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "range": Array [ + 168, + 169, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 5, + }, + "start": Object { + "column": 43, + "line": 5, + }, + }, + "range": Array [ + 169, + 170, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 53, + "line": 5, + }, + }, + "range": Array [ + 179, + 180, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "range": Array [ + 185, + 186, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "range": Array [ + 195, + 196, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 21, + "line": 6, + }, + }, + "range": Array [ + 202, + 203, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 45, + "line": 6, + }, + }, + "range": Array [ + 226, + 227, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 47, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "range": Array [ + 227, + 228, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 48, + "line": 6, + }, + "start": Object { + "column": 47, + "line": 6, + }, + }, + "range": Array [ + 228, + 229, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 57, + "line": 6, + }, + }, + "range": Array [ + 238, + 239, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "type": "JSXText", + "value": " + + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "range": Array [ + 245, + 246, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "range": Array [ + 255, + 256, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 21, + "line": 8, + }, + }, + "range": Array [ + 262, + 263, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 10, + }, + }, + "range": Array [ + 286, + 287, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "range": Array [ + 287, + 288, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 10, + }, + "start": Object { + "column": 6, + "line": 10, + }, + }, + "range": Array [ + 288, + 289, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 16, + "line": 10, + }, + }, + "range": Array [ + 298, + 299, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 11, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "range": Array [ + 304, + 305, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "range": Array [ + 314, + 315, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 21, + "line": 11, + }, + }, + "range": Array [ + 321, + 322, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 14, + }, + }, + "range": Array [ + 355, + 356, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "range": Array [ + 356, + 357, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 14, + }, + "start": Object { + "column": 6, + "line": 14, + }, + }, + "range": Array [ + 357, + 358, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 16, + "line": 14, + }, + }, + "range": Array [ + 367, + 368, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 15, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "range": Array [ + 373, + 374, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "range": Array [ + 383, + 384, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 21, + "line": 15, + }, + }, + "range": Array [ + 390, + 391, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 18, + }, + }, + "range": Array [ + 424, + 425, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "range": Array [ + 425, + 426, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 18, + }, + "start": Object { + "column": 6, + "line": 18, + }, + }, + "range": Array [ + 426, + 427, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 16, + "line": 18, + }, + }, + "range": Array [ + 436, + 437, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 19, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "range": Array [ + 442, + 443, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "range": Array [ + 452, + 453, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 21, + "line": 19, + }, + }, + "range": Array [ + 459, + 460, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 23, + }, + }, + "range": Array [ + 503, + 504, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "range": Array [ + 504, + 505, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 23, + }, + "start": Object { + "column": 6, + "line": 23, + }, + }, + "range": Array [ + 505, + 506, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 16, + "line": 23, + }, + }, + "range": Array [ + 515, + 516, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 520, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 24, + }, + "start": Object { + "column": 3, + "line": 24, + }, + }, + "range": Array [ + 520, + 521, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 4, + "line": 24, + }, + }, + "range": Array [ + 521, + 522, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 25, + }, + }, + "range": Array [ + 523, + 524, + ], + "type": "Punctuator", + "value": ")", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 1, + "line": 25, + }, + }, + "range": Array [ + 524, + 525, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", +} +`; + exports[`Comments fixtures/jsx-tag-comments.src 1`] = ` Object { "body": Array [ diff --git a/packages/shared-fixtures/fixtures/comments/jsx-generic-with-comment-in-tag.src.js b/packages/shared-fixtures/fixtures/comments/jsx-generic-with-comment-in-tag.src.js new file mode 100644 index 00000000000..ca252d971b4 --- /dev/null +++ b/packages/shared-fixtures/fixtures/comments/jsx-generic-with-comment-in-tag.src.js @@ -0,0 +1,25 @@ +const comp = ( + <> + /* comment1 */> + foo /* comment2 */> + /* comment3 */ bar> + foo /* comment4 */ bar> + + + // comment5 + > + + foo + // comment6 + > + + // comment7 + foo + > + + foo + // comment8 + bar + > + +); diff --git a/packages/typescript-estree/src/convert-comments.ts b/packages/typescript-estree/src/convert-comments.ts index 026500da7d3..15900403914 100644 --- a/packages/typescript-estree/src/convert-comments.ts +++ b/packages/typescript-estree/src/convert-comments.ts @@ -121,6 +121,8 @@ export function convertComments( container && container.parent && container.parent.kind === ts.SyntaxKind.JsxOpeningElement && + // Make sure this is the end of the opening element and not type parameter + end === container.parent.end && container.parent.parent && container.parent.parent.kind === ts.SyntaxKind.JsxElement ) { diff --git a/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap index 7c05def7e8f..ba119ad7aa5 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/comments.ts.snap @@ -3004,6 +3004,3732 @@ Object { } `; +exports[`Comments fixtures/jsx-generic-with-comment-in-tag.src 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "name": "comp", + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + }, + "init": Object { + "children": Array [ + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 58, + 70, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "name": "Component", + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 24, + 58, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 34, + 42, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 24, + 70, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 113, + 125, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "name": "foo", + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "name": "Component", + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 75, + 113, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 85, + 93, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 75, + 125, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 168, + 180, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "name": "bar", + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "name": "Component", + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 130, + 168, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 140, + 148, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 130, + 180, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 227, + 239, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "name": "foo", + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "name": "bar", + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "name": "Component", + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 185, + 227, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 195, + 203, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 185, + 239, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "raw": " + + ", + "type": "Literal", + "value": " + + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "name": "Component", + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 287, + 299, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "openingElement": Object { + "attributes": Array [], + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "name": "Component", + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 245, + 287, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 255, + 263, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 245, + 299, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "name": "Component", + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 356, + 368, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "name": "foo", + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "name": "Component", + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 304, + 356, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 314, + 322, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 304, + 368, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "name": "Component", + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 425, + 437, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "name": "foo", + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "name": "Component", + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 373, + 425, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 383, + 391, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 373, + 437, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + Object { + "children": Array [], + "closingElement": Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "name": "Component", + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 504, + 516, + ], + "type": "JSXClosingElement", + }, + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "openingElement": Object { + "attributes": Array [ + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "name": "foo", + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXAttribute", + "value": null, + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "name": "bar", + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXAttribute", + "value": null, + }, + ], + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "name": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "name": "Component", + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + }, + "range": Array [ + 442, + 504, + ], + "selfClosing": false, + "type": "JSXOpeningElement", + "typeParameters": Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "params": Array [ + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "TSNumberKeyword", + }, + ], + "range": Array [ + 452, + 460, + ], + "type": "TSTypeParameterInstantiation", + }, + }, + "range": Array [ + 442, + 516, + ], + "type": "JSXElement", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "raw": " + ", + "type": "Literal", + "value": " + ", + }, + ], + "closingFragment": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 522, + ], + "type": "JSXClosingFragment", + }, + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "openingFragment": Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 19, + ], + "type": "JSXOpeningFragment", + }, + "range": Array [ + 17, + 522, + ], + "type": "JSXFragment", + }, + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 524, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 525, + ], + "type": "VariableDeclaration", + }, + ], + "comments": Array [ + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 3, + }, + "start": Object { + "column": 23, + "line": 3, + }, + }, + "range": Array [ + 43, + 57, + ], + "type": "Block", + "value": " comment1 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 4, + }, + "start": Object { + "column": 27, + "line": 4, + }, + }, + "range": Array [ + 98, + 112, + ], + "type": "Block", + "value": " comment2 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 37, + "line": 5, + }, + "start": Object { + "column": 23, + "line": 5, + }, + }, + "range": Array [ + 149, + 163, + ], + "type": "Block", + "value": " comment3 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 6, + }, + "start": Object { + "column": 27, + "line": 6, + }, + }, + "range": Array [ + 208, + 222, + ], + "type": "Block", + "value": " comment4 ", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 9, + }, + "start": Object { + "column": 6, + "line": 9, + }, + }, + "range": Array [ + 270, + 281, + ], + "type": "Line", + "value": " comment5", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 13, + }, + "start": Object { + "column": 6, + "line": 13, + }, + }, + "range": Array [ + 339, + 350, + ], + "type": "Line", + "value": " comment6", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 16, + }, + "start": Object { + "column": 6, + "line": 16, + }, + }, + "range": Array [ + 398, + 409, + ], + "type": "Line", + "value": " comment7", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 21, + }, + "start": Object { + "column": 6, + "line": 21, + }, + }, + "range": Array [ + 477, + 488, + ], + "type": "Line", + "value": " comment8", + }, + ], + "loc": Object { + "end": Object { + "column": 0, + "line": 26, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 526, + ], + "sourceType": "script", + "tokens": Array [ + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 5, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 10, + ], + "type": "Identifier", + "value": "comp", + }, + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 1, + }, + "start": Object { + "column": 11, + "line": 1, + }, + }, + "range": Array [ + 11, + 12, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 13, + "line": 1, + }, + }, + "range": Array [ + 13, + 14, + ], + "type": "Punctuator", + "value": "(", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 2, + }, + "start": Object { + "column": 2, + "line": 2, + }, + }, + "range": Array [ + 17, + 18, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 2, + }, + "start": Object { + "column": 3, + "line": 2, + }, + }, + "range": Array [ + 18, + 19, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 2, + }, + }, + "range": Array [ + 19, + 24, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 3, + }, + "start": Object { + "column": 4, + "line": 3, + }, + }, + "range": Array [ + 24, + 25, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 5, + "line": 3, + }, + }, + "range": Array [ + 25, + 34, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 3, + }, + "start": Object { + "column": 14, + "line": 3, + }, + }, + "range": Array [ + 34, + 35, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 35, + 41, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 3, + }, + "start": Object { + "column": 21, + "line": 3, + }, + }, + "range": Array [ + 41, + 42, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 38, + "line": 3, + }, + "start": Object { + "column": 37, + "line": 3, + }, + }, + "range": Array [ + 57, + 58, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 39, + "line": 3, + }, + "start": Object { + "column": 38, + "line": 3, + }, + }, + "range": Array [ + 58, + 59, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 40, + "line": 3, + }, + "start": Object { + "column": 39, + "line": 3, + }, + }, + "range": Array [ + 59, + 60, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 49, + "line": 3, + }, + "start": Object { + "column": 40, + "line": 3, + }, + }, + "range": Array [ + 60, + 69, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 50, + "line": 3, + }, + "start": Object { + "column": 49, + "line": 3, + }, + }, + "range": Array [ + 69, + 70, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 4, + }, + "start": Object { + "column": 50, + "line": 3, + }, + }, + "range": Array [ + 70, + 75, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 4, + }, + "start": Object { + "column": 4, + "line": 4, + }, + }, + "range": Array [ + 75, + 76, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 5, + "line": 4, + }, + }, + "range": Array [ + 76, + 85, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "range": Array [ + 85, + 86, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 4, + }, + "start": Object { + "column": 15, + "line": 4, + }, + }, + "range": Array [ + 86, + 92, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 4, + }, + "start": Object { + "column": 21, + "line": 4, + }, + }, + "range": Array [ + 92, + 93, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 4, + }, + "start": Object { + "column": 23, + "line": 4, + }, + }, + "range": Array [ + 94, + 97, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 4, + }, + "start": Object { + "column": 41, + "line": 4, + }, + }, + "range": Array [ + 112, + 113, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 4, + }, + "start": Object { + "column": 42, + "line": 4, + }, + }, + "range": Array [ + 113, + 114, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 4, + }, + "start": Object { + "column": 43, + "line": 4, + }, + }, + "range": Array [ + 114, + 115, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 4, + }, + "start": Object { + "column": 44, + "line": 4, + }, + }, + "range": Array [ + 115, + 124, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 4, + }, + "start": Object { + "column": 53, + "line": 4, + }, + }, + "range": Array [ + 124, + 125, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 5, + }, + "start": Object { + "column": 54, + "line": 4, + }, + }, + "range": Array [ + 125, + 130, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 5, + }, + "start": Object { + "column": 4, + "line": 5, + }, + }, + "range": Array [ + 130, + 131, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 5, + }, + "start": Object { + "column": 5, + "line": 5, + }, + }, + "range": Array [ + 131, + 140, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "range": Array [ + 140, + 141, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 141, + 147, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 5, + }, + "start": Object { + "column": 21, + "line": 5, + }, + }, + "range": Array [ + 147, + 148, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 41, + "line": 5, + }, + "start": Object { + "column": 38, + "line": 5, + }, + }, + "range": Array [ + 164, + 167, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 42, + "line": 5, + }, + "start": Object { + "column": 41, + "line": 5, + }, + }, + "range": Array [ + 167, + 168, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 43, + "line": 5, + }, + "start": Object { + "column": 42, + "line": 5, + }, + }, + "range": Array [ + 168, + 169, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 44, + "line": 5, + }, + "start": Object { + "column": 43, + "line": 5, + }, + }, + "range": Array [ + 169, + 170, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 53, + "line": 5, + }, + "start": Object { + "column": 44, + "line": 5, + }, + }, + "range": Array [ + 170, + 179, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 54, + "line": 5, + }, + "start": Object { + "column": 53, + "line": 5, + }, + }, + "range": Array [ + 179, + 180, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 6, + }, + "start": Object { + "column": 54, + "line": 5, + }, + }, + "range": Array [ + 180, + 185, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 6, + }, + "start": Object { + "column": 4, + "line": 6, + }, + }, + "range": Array [ + 185, + 186, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 6, + }, + "start": Object { + "column": 5, + "line": 6, + }, + }, + "range": Array [ + 186, + 195, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 6, + }, + "start": Object { + "column": 14, + "line": 6, + }, + }, + "range": Array [ + 195, + 196, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 6, + }, + "start": Object { + "column": 15, + "line": 6, + }, + }, + "range": Array [ + 196, + 202, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 6, + }, + "start": Object { + "column": 21, + "line": 6, + }, + }, + "range": Array [ + 202, + 203, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 26, + "line": 6, + }, + "start": Object { + "column": 23, + "line": 6, + }, + }, + "range": Array [ + 204, + 207, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 45, + "line": 6, + }, + "start": Object { + "column": 42, + "line": 6, + }, + }, + "range": Array [ + 223, + 226, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 46, + "line": 6, + }, + "start": Object { + "column": 45, + "line": 6, + }, + }, + "range": Array [ + 226, + 227, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 47, + "line": 6, + }, + "start": Object { + "column": 46, + "line": 6, + }, + }, + "range": Array [ + 227, + 228, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 48, + "line": 6, + }, + "start": Object { + "column": 47, + "line": 6, + }, + }, + "range": Array [ + 228, + 229, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 57, + "line": 6, + }, + "start": Object { + "column": 48, + "line": 6, + }, + }, + "range": Array [ + 229, + 238, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 58, + "line": 6, + }, + "start": Object { + "column": 57, + "line": 6, + }, + }, + "range": Array [ + 238, + 239, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 8, + }, + "start": Object { + "column": 58, + "line": 6, + }, + }, + "range": Array [ + 239, + 245, + ], + "type": "JSXText", + "value": " + + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 4, + "line": 8, + }, + }, + "range": Array [ + 245, + 246, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "range": Array [ + 246, + 255, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 8, + }, + "start": Object { + "column": 14, + "line": 8, + }, + }, + "range": Array [ + 255, + 256, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 8, + }, + "start": Object { + "column": 15, + "line": 8, + }, + }, + "range": Array [ + 256, + 262, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 8, + }, + "start": Object { + "column": 21, + "line": 8, + }, + }, + "range": Array [ + 262, + 263, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 10, + }, + "start": Object { + "column": 4, + "line": 10, + }, + }, + "range": Array [ + 286, + 287, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 10, + }, + "start": Object { + "column": 5, + "line": 10, + }, + }, + "range": Array [ + 287, + 288, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 10, + }, + "start": Object { + "column": 6, + "line": 10, + }, + }, + "range": Array [ + 288, + 289, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 10, + }, + "start": Object { + "column": 7, + "line": 10, + }, + }, + "range": Array [ + 289, + 298, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 10, + }, + "start": Object { + "column": 16, + "line": 10, + }, + }, + "range": Array [ + 298, + 299, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 11, + }, + "start": Object { + "column": 17, + "line": 10, + }, + }, + "range": Array [ + 299, + 304, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 11, + }, + "start": Object { + "column": 4, + "line": 11, + }, + }, + "range": Array [ + 304, + 305, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 11, + }, + "start": Object { + "column": 5, + "line": 11, + }, + }, + "range": Array [ + 305, + 314, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 11, + }, + "start": Object { + "column": 14, + "line": 11, + }, + }, + "range": Array [ + 314, + 315, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 11, + }, + "start": Object { + "column": 15, + "line": 11, + }, + }, + "range": Array [ + 315, + 321, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 11, + }, + "start": Object { + "column": 21, + "line": 11, + }, + }, + "range": Array [ + 321, + 322, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 12, + }, + "start": Object { + "column": 6, + "line": 12, + }, + }, + "range": Array [ + 329, + 332, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 14, + }, + "start": Object { + "column": 4, + "line": 14, + }, + }, + "range": Array [ + 355, + 356, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 14, + }, + "start": Object { + "column": 5, + "line": 14, + }, + }, + "range": Array [ + 356, + 357, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 14, + }, + "start": Object { + "column": 6, + "line": 14, + }, + }, + "range": Array [ + 357, + 358, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 14, + }, + "start": Object { + "column": 7, + "line": 14, + }, + }, + "range": Array [ + 358, + 367, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 14, + }, + "start": Object { + "column": 16, + "line": 14, + }, + }, + "range": Array [ + 367, + 368, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 15, + }, + "start": Object { + "column": 17, + "line": 14, + }, + }, + "range": Array [ + 368, + 373, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 15, + }, + "start": Object { + "column": 4, + "line": 15, + }, + }, + "range": Array [ + 373, + 374, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 15, + }, + "start": Object { + "column": 5, + "line": 15, + }, + }, + "range": Array [ + 374, + 383, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 15, + }, + "start": Object { + "column": 14, + "line": 15, + }, + }, + "range": Array [ + 383, + 384, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 15, + }, + "start": Object { + "column": 15, + "line": 15, + }, + }, + "range": Array [ + 384, + 390, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 15, + }, + "start": Object { + "column": 21, + "line": 15, + }, + }, + "range": Array [ + 390, + 391, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 17, + }, + "start": Object { + "column": 6, + "line": 17, + }, + }, + "range": Array [ + 416, + 419, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 18, + }, + "start": Object { + "column": 4, + "line": 18, + }, + }, + "range": Array [ + 424, + 425, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 18, + }, + "start": Object { + "column": 5, + "line": 18, + }, + }, + "range": Array [ + 425, + 426, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 18, + }, + "start": Object { + "column": 6, + "line": 18, + }, + }, + "range": Array [ + 426, + 427, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 18, + }, + "start": Object { + "column": 7, + "line": 18, + }, + }, + "range": Array [ + 427, + 436, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 18, + }, + "start": Object { + "column": 16, + "line": 18, + }, + }, + "range": Array [ + 436, + 437, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 19, + }, + "start": Object { + "column": 17, + "line": 18, + }, + }, + "range": Array [ + 437, + 442, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 19, + }, + "start": Object { + "column": 4, + "line": 19, + }, + }, + "range": Array [ + 442, + 443, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 19, + }, + "start": Object { + "column": 5, + "line": 19, + }, + }, + "range": Array [ + 443, + 452, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 19, + }, + "start": Object { + "column": 14, + "line": 19, + }, + }, + "range": Array [ + 452, + 453, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 19, + }, + "start": Object { + "column": 15, + "line": 19, + }, + }, + "range": Array [ + 453, + 459, + ], + "type": "Identifier", + "value": "number", + }, + Object { + "loc": Object { + "end": Object { + "column": 22, + "line": 19, + }, + "start": Object { + "column": 21, + "line": 19, + }, + }, + "range": Array [ + 459, + 460, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 20, + }, + "start": Object { + "column": 6, + "line": 20, + }, + }, + "range": Array [ + 467, + 470, + ], + "type": "JSXIdentifier", + "value": "foo", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 22, + }, + "start": Object { + "column": 6, + "line": 22, + }, + }, + "range": Array [ + 495, + 498, + ], + "type": "JSXIdentifier", + "value": "bar", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 23, + }, + "start": Object { + "column": 4, + "line": 23, + }, + }, + "range": Array [ + 503, + 504, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 23, + }, + "start": Object { + "column": 5, + "line": 23, + }, + }, + "range": Array [ + 504, + 505, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 23, + }, + "start": Object { + "column": 6, + "line": 23, + }, + }, + "range": Array [ + 505, + 506, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 23, + }, + "start": Object { + "column": 7, + "line": 23, + }, + }, + "range": Array [ + 506, + 515, + ], + "type": "JSXIdentifier", + "value": "Component", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 23, + }, + "start": Object { + "column": 16, + "line": 23, + }, + }, + "range": Array [ + 515, + 516, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 24, + }, + "start": Object { + "column": 17, + "line": 23, + }, + }, + "range": Array [ + 516, + 519, + ], + "type": "JSXText", + "value": " + ", + }, + Object { + "loc": Object { + "end": Object { + "column": 3, + "line": 24, + }, + "start": Object { + "column": 2, + "line": 24, + }, + }, + "range": Array [ + 519, + 520, + ], + "type": "Punctuator", + "value": "<", + }, + Object { + "loc": Object { + "end": Object { + "column": 4, + "line": 24, + }, + "start": Object { + "column": 3, + "line": 24, + }, + }, + "range": Array [ + 520, + 521, + ], + "type": "Punctuator", + "value": "/", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 24, + }, + "start": Object { + "column": 4, + "line": 24, + }, + }, + "range": Array [ + 521, + 522, + ], + "type": "Punctuator", + "value": ">", + }, + Object { + "loc": Object { + "end": Object { + "column": 1, + "line": 25, + }, + "start": Object { + "column": 0, + "line": 25, + }, + }, + "range": Array [ + 523, + 524, + ], + "type": "Punctuator", + "value": ")", + }, + Object { + "loc": Object { + "end": Object { + "column": 2, + "line": 25, + }, + "start": Object { + "column": 1, + "line": 25, + }, + }, + "range": Array [ + 524, + 525, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", +} +`; + exports[`Comments fixtures/jsx-tag-comments.src 1`] = ` Object { "body": Array [ diff --git a/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap index efd3dd82094..de734d08383 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/semantic-diagnostics-enabled.ts.snap @@ -14,6 +14,8 @@ exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" e exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-comment-after-self-closing-jsx.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; +exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-generic-with-comment-in-tag.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; + exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-tag-comments.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/comments/jsx-text-with-multiline-non-comment.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; From 6489293e3ddab033264905b8fd3387cca80d5043 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 6 Jun 2019 20:59:34 -0700 Subject: [PATCH 40/41] fix(eslint-plugin): [no-type-alias] Fix parenthesized type handling (#576) --- .../eslint-plugin/src/rules/no-type-alias.ts | 183 ++++++++---------- .../tests/rules/no-type-alias.test.ts | 4 + 2 files changed, 88 insertions(+), 99 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index 92249006455..a4bf7fca2d3 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -1,6 +1,5 @@ import { AST_NODE_TYPES, - TSESLint, TSESTree, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; @@ -30,6 +29,14 @@ type Options = [ ]; type MessageIds = 'noTypeAlias' | 'noCompositionAlias'; +type CompositionType = + | AST_NODE_TYPES.TSUnionType + | AST_NODE_TYPES.TSIntersectionType; +interface TypeWithLabel { + node: TSESTree.Node; + compositionType: CompositionType | null; +} + export default util.createRule({ name: 'no-type-alias', meta: { @@ -106,24 +113,13 @@ export default util.createRule({ 'in-intersections', 'in-unions-and-intersections', ]; - const aliasTypes = [ + const aliasTypes = new Set([ AST_NODE_TYPES.TSArrayType, AST_NODE_TYPES.TSTypeReference, AST_NODE_TYPES.TSLiteralType, AST_NODE_TYPES.TSTypeQuery, - ]; - - type CompositionType = TSESTree.TSUnionType | TSESTree.TSIntersectionType; - /** - * Determines if the given node is a union or an intersection. - */ - function isComposition(node: TSESTree.TypeNode): node is CompositionType { - return ( - node && - (node.type === AST_NODE_TYPES.TSUnionType || - node.type === AST_NODE_TYPES.TSIntersectionType) - ); - } + AST_NODE_TYPES.TSIndexedAccessType, + ]); /** * Determines if the composition type is supported by the allowed flags. @@ -133,7 +129,7 @@ export default util.createRule({ */ function isSupportedComposition( isTopLevel: boolean, - compositionType: string | undefined, + compositionType: CompositionType | null, allowed: string, ): boolean { return ( @@ -146,43 +142,6 @@ export default util.createRule({ ); } - /** - * Determines if the given node is an alias type (keywords, arrays, type references and constants). - * @param node the node to be evaluated. - */ - function isAlias( - node: TSESTree.Node, - ): boolean /* not worth enumerating the ~25 individual types here */ { - return ( - node && - (/Keyword$/.test(node.type) || aliasTypes.indexOf(node.type) > -1) - ); - } - - /** - * Determines if the given node is a callback type. - * @param node the node to be evaluated. - */ - function isCallback(node: TSESTree.Node): node is TSESTree.TSFunctionType { - return node && node.type === AST_NODE_TYPES.TSFunctionType; - } - - /** - * Determines if the given node is a literal type (objects). - * @param node the node to be evaluated. - */ - function isLiteral(node: TSESTree.Node): node is TSESTree.TSTypeLiteral { - return node && node.type === AST_NODE_TYPES.TSTypeLiteral; - } - - /** - * Determines if the given node is a mapped type. - * @param node the node to be evaluated. - */ - function isMappedType(node: TSESTree.Node): node is TSESTree.TSMappedType { - return node && node.type === AST_NODE_TYPES.TSMappedType; - } - /** * Gets the message to be displayed based on the node type and whether the node is a top level declaration. * @param node the location @@ -191,23 +150,23 @@ export default util.createRule({ * @param isRoot a flag indicating we are dealing with the top level declaration. * @param type the kind of type alias being validated. */ - function getMessage( + function reportError( node: TSESTree.Node, - compositionType: string | undefined, + compositionType: CompositionType | null, isRoot: boolean, - type?: string, - ): TSESLint.ReportDescriptor { + type: string, + ): void { if (isRoot) { - return { + return context.report({ node, messageId: 'noTypeAlias', data: { - alias: type || 'aliases', + alias: type.toLowerCase(), }, - }; + }); } - return { + return context.report({ node, messageId: 'noCompositionAlias', data: { @@ -215,84 +174,110 @@ export default util.createRule({ compositionType === AST_NODE_TYPES.TSUnionType ? 'union' : 'intersection', - typeName: util.upperCaseFirst(type!), + typeName: type, }, - }; + }); } /** * Validates the node looking for aliases, callbacks and literals. * @param node the node to be validated. - * @param isTopLevel a flag indicating this is the top level node. - * @param compositionType the type of composition this alias is part of (undefined if not + * @param compositionType the type of composition this alias is part of (null if not * part of a composition) + * @param isTopLevel a flag indicating this is the top level node. */ function validateTypeAliases( - node: TSESTree.Node, - isTopLevel: boolean, - compositionType?: string, + type: TypeWithLabel, + isTopLevel: boolean = false, ): void { - if (isCallback(node)) { + if (type.node.type === AST_NODE_TYPES.TSFunctionType) { + // callback if (allowCallbacks === 'never') { - context.report( - getMessage(node, compositionType, isTopLevel, 'callbacks'), - ); + reportError(type.node, type.compositionType, isTopLevel, 'Callbacks'); } - } else if (isLiteral(node)) { + } else if (type.node.type === AST_NODE_TYPES.TSTypeLiteral) { + // literal object type if ( allowLiterals === 'never' || - !isSupportedComposition(isTopLevel, compositionType, allowLiterals!) + !isSupportedComposition( + isTopLevel, + type.compositionType, + allowLiterals!, + ) ) { - context.report( - getMessage(node, compositionType, isTopLevel, 'literals'), - ); + reportError(type.node, type.compositionType, isTopLevel, 'Literals'); } - } else if (isMappedType(node)) { + } else if (type.node.type === AST_NODE_TYPES.TSMappedType) { + // mapped type if ( allowMappedTypes === 'never' || !isSupportedComposition( isTopLevel, - compositionType, + type.compositionType, allowMappedTypes!, ) ) { - context.report( - getMessage(node, compositionType, isTopLevel, 'mapped types'), + reportError( + type.node, + type.compositionType, + isTopLevel, + 'Mapped types', ); } - } else if (isAlias(node)) { + } else if ( + /Keyword$/.test(type.node.type) || + aliasTypes.has(type.node.type) + ) { + // alias / keyword if ( allowAliases === 'never' || - !isSupportedComposition(isTopLevel, compositionType, allowAliases!) + !isSupportedComposition( + isTopLevel, + type.compositionType, + allowAliases!, + ) ) { - context.report( - getMessage(node, compositionType, isTopLevel, 'aliases'), - ); + reportError(type.node, type.compositionType, isTopLevel, 'Aliases'); } } else { - context.report(getMessage(node, compositionType, isTopLevel)); + // unhandled type - shouldn't happen + reportError(type.node, type.compositionType, isTopLevel, 'Unhandled'); } } /** - * Validates compositions (unions and/or intersections). + * Flatten the given type into an array of its dependencies */ - function validateCompositions(node: CompositionType): void { - node.types.forEach(type => { - if (isComposition(type)) { - validateCompositions(type); - } else { - validateTypeAliases(type, false, node.type); - } - }); + function getTypes( + node: TSESTree.Node, + compositionType: CompositionType | null = null, + ): TypeWithLabel[] { + if ( + node.type === AST_NODE_TYPES.TSUnionType || + node.type === AST_NODE_TYPES.TSIntersectionType + ) { + return node.types.reduce((acc, type) => { + acc.push(...getTypes(type, node.type)); + return acc; + }, []); + } + if (node.type === AST_NODE_TYPES.TSParenthesizedType) { + return getTypes(node.typeAnnotation, compositionType); + } + return [{ node, compositionType }]; } return { TSTypeAliasDeclaration(node) { - if (isComposition(node.typeAnnotation)) { - validateCompositions(node.typeAnnotation); + const types = getTypes(node.typeAnnotation); + if (types.length === 1) { + // is a top level type annotation + validateTypeAliases(types[0], true); } else { - validateTypeAliases(node.typeAnnotation, true); + // is a composition type + types.forEach(type => { + validateTypeAliases(type); + }); } }, }; diff --git a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts index d34cb9957b8..3c3d83520e8 100644 --- a/packages/eslint-plugin/tests/rules/no-type-alias.test.ts +++ b/packages/eslint-plugin/tests/rules/no-type-alias.test.ts @@ -7,6 +7,10 @@ const ruleTester = new RuleTester({ ruleTester.run('no-type-alias', rule, { valid: [ + { + code: "type A = 'a' & ('b' | 'c');", + options: [{ allowAliases: 'always' }], + }, { code: "type Foo = 'a';", options: [{ allowAliases: 'always' }], From b16409ab8b532fca61a61dc0a0b6abcff512dad5 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Thu, 6 Jun 2019 21:26:16 -0700 Subject: [PATCH 41/41] fix(eslint-plugin): [NUTA] false positive for null assign to undefined (#536) Fixes #529 --- .../rules/no-unnecessary-type-assertion.ts | 47 ++++++++++++---- packages/eslint-plugin/src/util/types.ts | 56 +++++++++++++++---- .../no-unnecessary-type-assertion.test.ts | 9 ++- 3 files changed, 89 insertions(+), 23 deletions(-) diff --git a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts index 765fd308653..54bb2d6cc92 100644 --- a/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-unnecessary-type-assertion.ts @@ -188,18 +188,43 @@ export default util.createRule({ } else { // we know it's a nullable type // so figure out if the variable is used in a place that accepts nullable types + const contextualType = getContextualType(checker, originalNode); - if (contextualType && util.isNullableType(contextualType)) { - context.report({ - node, - messageId: 'contextuallyUnnecessary', - fix(fixer) { - return fixer.removeRange([ - originalNode.expression.end, - originalNode.end, - ]); - }, - }); + if (contextualType) { + // in strict mode you can't assign null to undefined, so we have to make sure that + // the two types share a nullable type + const typeIncludesUndefined = util.isTypeFlagSet( + type, + ts.TypeFlags.Undefined, + ); + const typeIncludesNull = util.isTypeFlagSet( + type, + ts.TypeFlags.Null, + ); + + const contextualTypeIncludesUndefined = util.isTypeFlagSet( + contextualType, + ts.TypeFlags.Undefined, + ); + const contextualTypeIncludesNull = util.isTypeFlagSet( + contextualType, + ts.TypeFlags.Null, + ); + if ( + (typeIncludesUndefined && contextualTypeIncludesUndefined) || + (typeIncludesNull && contextualTypeIncludesNull) + ) { + context.report({ + node, + messageId: 'contextuallyUnnecessary', + fix(fixer) { + return fixer.removeRange([ + originalNode.expression.end, + originalNode.end, + ]); + }, + }); + } } } }, diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index 55567f00aea..98ae09b02c4 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -1,5 +1,4 @@ import { - isTypeFlagSet, isTypeReference, isUnionOrIntersectionType, unionTypeParts, @@ -115,18 +114,24 @@ export function getConstrainedTypeAtLocation( * Checks if the given type is (or accepts) nullable * @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter) */ -export function isNullableType(type: ts.Type, isReceiver?: boolean): boolean { - let flags: ts.TypeFlags = 0; - for (const t of unionTypeParts(type)) { - flags |= t.flags; - } +export function isNullableType( + type: ts.Type, + { + isReceiver = false, + allowUndefined = true, + }: { isReceiver?: boolean; allowUndefined?: boolean } = {}, +): boolean { + const flags = getTypeFlags(type); - flags = - isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown) - ? -1 - : flags; + if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { + return true; + } - return (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; + if (allowUndefined) { + return (flags & (ts.TypeFlags.Null | ts.TypeFlags.Undefined)) !== 0; + } else { + return (flags & ts.TypeFlags.Null) !== 0; + } } /** @@ -138,3 +143,32 @@ export function getDeclaration( ): ts.Declaration { return checker.getSymbolAtLocation(node)!.declarations![0]; } + +/** + * Gets all of the type flags in a type, iterating through unions automatically + */ +export function getTypeFlags(type: ts.Type): ts.TypeFlags { + let flags: ts.TypeFlags = 0; + for (const t of unionTypeParts(type)) { + flags |= t.flags; + } + return flags; +} + +/** + * Checks if the given type is (or accepts) the given flags + * @param isReceiver true if the type is a receiving type (i.e. the type of a called function's parameter) + */ +export function isTypeFlagSet( + type: ts.Type, + flagsToCheck: ts.TypeFlags, + isReceiver?: boolean, +): boolean { + const flags = getTypeFlags(type); + + if (isReceiver && flags & (ts.TypeFlags.Any | ts.TypeFlags.Unknown)) { + return true; + } + + return (flags & flagsToCheck) !== 0; +} diff --git a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts index 7106fc461b4..7ed69a441f4 100644 --- a/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts +++ b/packages/eslint-plugin/tests/rules/no-unnecessary-type-assertion.test.ts @@ -2,7 +2,7 @@ import path from 'path'; import rule from '../../src/rules/no-unnecessary-type-assertion'; import { RuleTester } from '../RuleTester'; -const rootDir = path.join(process.cwd(), 'tests/fixtures'); +const rootDir = path.resolve(__dirname, '../fixtures/'); const ruleTester = new RuleTester({ parserOptions: { ecmaVersion: 2015, @@ -88,6 +88,13 @@ class Foo { prop: number = x!; } `, + // https://github.com/typescript-eslint/typescript-eslint/issues/529 + ` +declare function foo(str?: string): void; +declare const str: string | null; + +foo(str!); + `, ], invalid: [