From 76b89a50489217ced0250a046969b557cc2e6a6a Mon Sep 17 00:00:00 2001 From: Josh Goldberg Date: Mon, 24 Jun 2019 11:14:14 -0500 Subject: [PATCH 01/13] feat(eslint-plugin): added new rule prefer-readonly (#555) * feat(eslint-plugin): added new rule prefer-readonly Adds the equivalent of TSLint's `prefer-readonly` rule. * Added docs, auto-fixing * Updated docs; love the new build time checks! * Fixed linting errors (ha) and corrected internal source * PR feedback: non recommended; :exit; some test coverage * I guess tslintRuleName isn't allowed now? * Added back recommended as false * Removed :exit; fixed README.md table --- .../eslint-plugin-tslint/src/custom-linter.ts | 2 +- packages/eslint-plugin/README.md | 1 + packages/eslint-plugin/ROADMAP.md | 3 +- .../docs/rules/prefer-readonly.md | 81 +++ packages/eslint-plugin/src/configs/all.json | 1 + .../indent-new-do-not-use/OffsetStorage.ts | 14 +- .../rules/indent-new-do-not-use/TokenInfo.ts | 2 +- packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/prefer-readonly.ts | 335 +++++++++++ packages/eslint-plugin/src/util/types.ts | 27 + .../tests/rules/prefer-readonly.test.ts | 549 ++++++++++++++++++ packages/typescript-estree/src/convert.ts | 6 +- 12 files changed, 1010 insertions(+), 13 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/prefer-readonly.md create mode 100644 packages/eslint-plugin/src/rules/prefer-readonly.ts create mode 100644 packages/eslint-plugin/tests/rules/prefer-readonly.test.ts diff --git a/packages/eslint-plugin-tslint/src/custom-linter.ts b/packages/eslint-plugin-tslint/src/custom-linter.ts index 892e12f1adf0..833fda00ca92 100644 --- a/packages/eslint-plugin-tslint/src/custom-linter.ts +++ b/packages/eslint-plugin-tslint/src/custom-linter.ts @@ -4,7 +4,7 @@ import { Program } from 'typescript'; const TSLintLinter = Linter as any; export class CustomLinter extends TSLintLinter { - constructor(options: ILinterOptions, private program: Program) { + constructor(options: ILinterOptions, private readonly program: Program) { super(options, program); } diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 68ff8434d8a7..d5163109b1e6 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -171,6 +171,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@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-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-readonly`](./docs/rules/prefer-readonly.md) | Requires that private members are marked as `readonly` if they're never modified outside of the constructor | | :wrench: | :thought_balloon: | | [`@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: | diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index d47abb01d051..8a527cd33132 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -122,7 +122,7 @@ | [`no-require-imports`] | ✅ | [`@typescript-eslint/no-require-imports`] | | [`object-literal-sort-keys`] | 🌓 | [`sort-keys`][sort-keys] [2] | | [`prefer-const`] | 🌟 | [`prefer-const`][prefer-const] | -| [`prefer-readonly`] | 🛑 | N/A | +| [`prefer-readonly`] | ✅ | [`@typescript-eslint/prefer-readonly`] | | [`trailing-comma`] | 🌓 | [`comma-dangle`][comma-dangle] or [Prettier] | [1] Only warns when importing deprecated symbols
@@ -611,6 +611,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/prefer-interface`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-interface.md [`@typescript-eslint/no-array-constructor`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-array-constructor.md [`@typescript-eslint/prefer-function-type`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-function-type.md +[`@typescript-eslint/prefer-readonly`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-readonly.md [`@typescript-eslint/no-for-in-array`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-for-in-array.md [`@typescript-eslint/no-unnecessary-qualifier`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-qualifier.md [`@typescript-eslint/semi`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/semi.md diff --git a/packages/eslint-plugin/docs/rules/prefer-readonly.md b/packages/eslint-plugin/docs/rules/prefer-readonly.md new file mode 100644 index 000000000000..b3f0a5420109 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-readonly.md @@ -0,0 +1,81 @@ +# require never-modified private members be marked as `readonly` + +This rule enforces that private members are marked as `readonly` if they're never modified outside of the constructor. + +## Rule Details + +Member variables with the privacy `private` are never permitted to be modified outside of their declaring class. +If that class never modifies their value, they may safely be marked as `readonly`. + +Examples of **incorrect** code for this rule: + +```ts +class Container { + // These member variables could be marked as readonly + private neverModifiedMember = true; + private onlyModifiedInConstructor: number; + + public constructor( + onlyModifiedInConstructor: number, + // Private parameter properties can also be marked as reaodnly + private neverModifiedParameter: string, + ) { + this.onlyModifiedInConstructor = onlyModifiedInConstructor; + } +} +``` + +Examples of **correct** code for this rule: + +```ts +class Container { + // Public members might be modified externally + public publicMember: boolean; + + // Protected members might be modified by child classes + protected protectedMember: number; + + // This is modified later on by the class + private modifiedLater = 'unchanged'; + + public mutate() { + this.modifiedLater = 'mutated'; + } +} +``` + +## Options + +This rule, in its default state, does not require any argument. + +### onlyInlineLambdas + +You may pass `"onlyInlineLambdas": true` as a rule option within an object to restrict checking only to members immediately assigned a lambda value. + +```cjson +{ + "@typescript-eslint/prefer-readonly": ["error", { "onlyInlineLambdas": true }] +} +``` + +Example of **correct** code for the `{ "onlyInlineLambdas": true }` options: + +```ts +class Container { + private neverModifiedPrivate = 'unchanged'; +} +``` + +Example of **incorrect** code for the `{ "onlyInlineLambdas": true }` options: + +```ts +class Container { + private onClick = () => { + /* ... */ + }; +} +``` + +## Related to + +- TSLint: ['prefer-readonly'](https://palantir.github.io/tslint/rules/prefer-readonly) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 2efc8d24323f..7dc471fdd5d0 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -57,6 +57,7 @@ "@typescript-eslint/prefer-function-type": "error", "@typescript-eslint/prefer-includes": "error", "@typescript-eslint/prefer-namespace-keyword": "error", + "@typescript-eslint/prefer-readonly": "error", "@typescript-eslint/prefer-regexp-exec": "error", "@typescript-eslint/prefer-string-starts-ends-with": "error", "@typescript-eslint/promise-function-async": "error", 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 deacc272bb33..475e3c35d7b0 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 @@ -9,13 +9,13 @@ import { TokenInfo } from './TokenInfo'; * 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; + private readonly tokenInfo: TokenInfo; + private readonly indentSize: number; + private readonly indentType: string; + private readonly tree: BinarySearchTree; + private readonly lockedFirstTokens: WeakMap; + private readonly desiredIndentCache: WeakMap; + private readonly ignoredTokens: WeakSet; /** * @param tokenInfo a TokenInfo instance * @param indentSize The desired size of each indentation level 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 9b7d345fe3d6..16d15c4ae5f1 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 @@ -8,7 +8,7 @@ import { TokenOrComment } from './BinarySearchTree'; * A helper class to get token-based info related to indentation */ export class TokenInfo { - private sourceCode: TSESLint.SourceCode; + private readonly sourceCode: TSESLint.SourceCode; public firstTokensByLineNumber: Map; constructor(sourceCode: TSESLint.SourceCode) { diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 3b799b1cc80b..5d6fc99aa486 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -46,6 +46,7 @@ import preferFunctionType from './prefer-function-type'; import preferIncludes from './prefer-includes'; import preferInterface from './prefer-interface'; import preferNamespaceKeyword from './prefer-namespace-keyword'; +import preferReadonly from './prefer-readonly'; import preferRegexpExec from './prefer-regexp-exec'; import preferStringStartsEndsWith from './prefer-string-starts-ends-with'; import promiseFunctionAsync from './promise-function-async'; @@ -105,6 +106,7 @@ export default { 'prefer-includes': preferIncludes, 'prefer-interface': preferInterface, 'prefer-namespace-keyword': preferNamespaceKeyword, + 'prefer-readonly': preferReadonly, 'prefer-regexp-exec': preferRegexpExec, 'prefer-string-starts-ends-with': preferStringStartsEndsWith, 'promise-function-async': promiseFunctionAsync, diff --git a/packages/eslint-plugin/src/rules/prefer-readonly.ts b/packages/eslint-plugin/src/rules/prefer-readonly.ts new file mode 100644 index 000000000000..24685d306762 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-readonly.ts @@ -0,0 +1,335 @@ +import * as tsutils from 'tsutils'; +import ts from 'typescript'; +import * as util from '../util'; +import { typeIsOrHasBaseType } from '../util'; +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; + +type MessageIds = 'preferReadonly'; + +type Options = [ + { + onlyInlineLambdas?: boolean; + } +]; + +const functionScopeBoundaries = [ + 'ArrowFunctionExpression', + 'FunctionDeclaration', + 'FunctionExpression', + 'GetAccessor', + 'MethodDefinition', + 'SetAccessor', +].join(', '); + +export default util.createRule({ + name: 'prefer-readonly', + meta: { + docs: { + description: + "Requires that private members are marked as `readonly` if they're never modified outside of the constructor", + category: 'Best Practices', + recommended: false, + }, + fixable: 'code', + messages: { + preferReadonly: + "Member '{{name}}' is never reassigned; mark it as `readonly`.", + }, + schema: [ + { + allowAdditionalProperties: false, + properties: { + onlyInlineLambdas: { + type: 'boolean', + }, + }, + type: 'object', + }, + ], + type: 'suggestion', + }, + defaultOptions: [{ onlyInlineLambdas: false }], + create(context, [{ onlyInlineLambdas }]) { + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + const classScopeStack: ClassScope[] = []; + + function handlePropertyAccessExpression( + node: ts.PropertyAccessExpression, + parent: ts.Node, + classScope: ClassScope, + ) { + if (ts.isBinaryExpression(parent)) { + handleParentBinaryExpression(node, parent, classScope); + return; + } + + if (ts.isDeleteExpression(parent)) { + classScope.addVariableModification(node); + return; + } + + if ( + ts.isPostfixUnaryExpression(parent) || + ts.isPrefixUnaryExpression(parent) + ) { + handleParentPostfixOrPrefixUnaryExpression(parent, classScope); + } + } + + function handleParentBinaryExpression( + node: ts.PropertyAccessExpression, + parent: ts.BinaryExpression, + classScope: ClassScope, + ) { + if ( + parent.left === node && + tsutils.isAssignmentKind(parent.operatorToken.kind) + ) { + classScope.addVariableModification(node); + } + } + + function handleParentPostfixOrPrefixUnaryExpression( + node: ts.PostfixUnaryExpression | ts.PrefixUnaryExpression, + classScope: ClassScope, + ) { + if ( + node.operator === ts.SyntaxKind.PlusPlusToken || + node.operator === ts.SyntaxKind.MinusMinusToken + ) { + classScope.addVariableModification( + node.operand as ts.PropertyAccessExpression, + ); + } + } + + function isConstructor(node: TSESTree.Node) { + return ( + node.type === AST_NODE_TYPES.MethodDefinition && + node.kind === 'constructor' + ); + } + + function isFunctionScopeBoundaryInStack(node: TSESTree.Node) { + if (classScopeStack.length === 0) { + return false; + } + + const tsNode = parserServices.esTreeNodeToTSNodeMap.get(node); + if (ts.isConstructorDeclaration(tsNode)) { + return false; + } + + return tsutils.isFunctionScopeBoundary(tsNode); + } + + function getEsNodesFromViolatingNode( + violatingNode: ParameterOrPropertyDeclaration, + ) { + if (ts.isParameterPropertyDeclaration(violatingNode)) { + return { + esNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode.name), + nameNode: parserServices.tsNodeToESTreeNodeMap.get( + violatingNode.name, + ), + }; + } + + return { + esNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode), + nameNode: parserServices.tsNodeToESTreeNodeMap.get(violatingNode.name), + }; + } + + return { + 'ClassDeclaration, ClassExpression'( + node: TSESTree.ClassDeclaration | TSESTree.ClassExpression, + ) { + classScopeStack.push( + new ClassScope( + checker, + parserServices.esTreeNodeToTSNodeMap.get(node), + onlyInlineLambdas, + ), + ); + }, + 'ClassDeclaration, ClassExpression:exit'() { + const finalizedClassScope = classScopeStack.pop()!; + const sourceCode = context.getSourceCode(); + + for (const violatingNode of finalizedClassScope.finalizeUnmodifiedPrivateNonReadonlys()) { + const { esNode, nameNode } = getEsNodesFromViolatingNode( + violatingNode, + ); + context.report({ + data: { + name: sourceCode.getText(nameNode), + }, + fix: fixer => fixer.insertTextBefore(nameNode, 'readonly '), + messageId: 'preferReadonly', + node: esNode, + }); + } + }, + MemberExpression(node) { + const tsNode = parserServices.esTreeNodeToTSNodeMap.get< + ts.PropertyAccessExpression + >(node); + if (classScopeStack.length !== 0) { + handlePropertyAccessExpression( + tsNode, + tsNode.parent, + classScopeStack[classScopeStack.length - 1], + ); + } + }, + [functionScopeBoundaries](node: TSESTree.Node) { + if (isConstructor(node)) { + classScopeStack[classScopeStack.length - 1].enterConstructor( + parserServices.esTreeNodeToTSNodeMap.get( + node, + ), + ); + } else if (isFunctionScopeBoundaryInStack(node)) { + classScopeStack[classScopeStack.length - 1].enterNonConstructor(); + } + }, + [`${functionScopeBoundaries}:exit`](node: TSESTree.Node) { + if (isConstructor(node)) { + classScopeStack[classScopeStack.length - 1].exitConstructor(); + } else if (isFunctionScopeBoundaryInStack(node)) { + classScopeStack[classScopeStack.length - 1].exitNonConstructor(); + } + }, + }; + }, +}); + +type ParameterOrPropertyDeclaration = + | ts.ParameterDeclaration + | ts.PropertyDeclaration; + +const OUTSIDE_CONSTRUCTOR = -1; +const DIRECTLY_INSIDE_CONSTRUCTOR = 0; + +class ClassScope { + private readonly privateModifiableMembers = new Map< + string, + ParameterOrPropertyDeclaration + >(); + private readonly privateModifiableStatics = new Map< + string, + ParameterOrPropertyDeclaration + >(); + private readonly memberVariableModifications = new Set(); + private readonly staticVariableModifications = new Set(); + + private readonly classType: ts.Type; + + private constructorScopeDepth = OUTSIDE_CONSTRUCTOR; + + public constructor( + private readonly checker: ts.TypeChecker, + classNode: ts.ClassLikeDeclaration, + private readonly onlyInlineLambdas?: boolean, + ) { + this.checker = checker; + this.classType = checker.getTypeAtLocation(classNode); + + for (const member of classNode.members) { + if (ts.isPropertyDeclaration(member)) { + this.addDeclaredVariable(member); + } + } + } + + public addDeclaredVariable(node: ParameterOrPropertyDeclaration) { + if ( + !tsutils.isModifierFlagSet(node, ts.ModifierFlags.Private) || + tsutils.isModifierFlagSet(node, ts.ModifierFlags.Readonly) || + ts.isComputedPropertyName(node.name) + ) { + return; + } + + if ( + this.onlyInlineLambdas && + node.initializer !== undefined && + !ts.isArrowFunction(node.initializer) + ) { + return; + } + + (tsutils.isModifierFlagSet(node, ts.ModifierFlags.Static) + ? this.privateModifiableStatics + : this.privateModifiableMembers + ).set(node.name.getText(), node); + } + + public addVariableModification(node: ts.PropertyAccessExpression) { + const modifierType = this.checker.getTypeAtLocation(node.expression); + if ( + modifierType.symbol === undefined || + !typeIsOrHasBaseType(modifierType, this.classType) + ) { + return; + } + + const modifyingStatic = + tsutils.isObjectType(modifierType) && + tsutils.isObjectFlagSet(modifierType, ts.ObjectFlags.Anonymous); + if ( + !modifyingStatic && + this.constructorScopeDepth === DIRECTLY_INSIDE_CONSTRUCTOR + ) { + return; + } + + (modifyingStatic + ? this.staticVariableModifications + : this.memberVariableModifications + ).add(node.name.text); + } + + public enterConstructor(node: ts.ConstructorDeclaration) { + this.constructorScopeDepth = DIRECTLY_INSIDE_CONSTRUCTOR; + + for (const parameter of node.parameters) { + if (tsutils.isModifierFlagSet(parameter, ts.ModifierFlags.Private)) { + this.addDeclaredVariable(parameter); + } + } + } + + public exitConstructor() { + this.constructorScopeDepth = OUTSIDE_CONSTRUCTOR; + } + + public enterNonConstructor() { + if (this.constructorScopeDepth !== OUTSIDE_CONSTRUCTOR) { + this.constructorScopeDepth += 1; + } + } + + public exitNonConstructor() { + if (this.constructorScopeDepth !== OUTSIDE_CONSTRUCTOR) { + this.constructorScopeDepth -= 1; + } + } + + public finalizeUnmodifiedPrivateNonReadonlys() { + this.memberVariableModifications.forEach(variableName => { + this.privateModifiableMembers.delete(variableName); + }); + + this.staticVariableModifications.forEach(variableName => { + this.privateModifiableStatics.delete(variableName); + }); + + return [ + ...Array.from(this.privateModifiableMembers.values()), + ...Array.from(this.privateModifiableStatics.values()), + ]; + } +} diff --git a/packages/eslint-plugin/src/util/types.ts b/packages/eslint-plugin/src/util/types.ts index f10bda7f731b..83a8bbabf575 100644 --- a/packages/eslint-plugin/src/util/types.ts +++ b/packages/eslint-plugin/src/util/types.ts @@ -182,3 +182,30 @@ export function isTypeFlagSet( return (flags & flagsToCheck) !== 0; } + +/** + * @returns Whether a type is an instance of the parent type, including for the parent's base types. + */ +export const typeIsOrHasBaseType = (type: ts.Type, parentType: ts.Type) => { + if (type.symbol === undefined || parentType.symbol === undefined) { + return false; + } + + const typeAndBaseTypes = [type]; + const ancestorTypes = type.getBaseTypes(); + + if (ancestorTypes !== undefined) { + typeAndBaseTypes.push(...ancestorTypes); + } + + for (const baseType of typeAndBaseTypes) { + if ( + baseType.symbol !== undefined && + baseType.symbol.name === parentType.symbol.name + ) { + return true; + } + } + + return false; +}; diff --git a/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts new file mode 100644 index 000000000000..a5ff4734e1c5 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/prefer-readonly.test.ts @@ -0,0 +1,549 @@ +import rule from '../../src/rules/prefer-readonly'; +import { RuleTester, getFixturesRootDir } from '../RuleTester'; + +const rootDir = getFixturesRootDir(); +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + ecmaVersion: 2015, + tsconfigRootDir: rootDir, + project: './tsconfig.json', + }, +}); + +ruleTester.run('prefer-readonly', rule, { + valid: [ + `function ignore() { }`, + `const ignore = function () { }`, + `const ignore = () => { }`, + `const container = { member: true }; + container.member;`, + `const container = { member: 1 }; + +container.member;`, + `const container = { member: 1 }; + ++container.member;`, + `const container = { member: 1 }; + container.member++;`, + `const container = { member: 1 }; + -container.member;`, + `const container = { member: 1 }; + --container.member;`, + `const container = { member: 1 }; + container.member--;`, + `class TestEmpty { }`, + `class TestReadonlyStatic { + private static readonly correctlyReadonlyStatic = 7; + }`, + `class TestModifiableStatic { + private static correctlyModifiableStatic = 7; + + public constructor() { + TestModifiableStatic.correctlyModifiableStatic += 1; + } + }`, + `class TestModifiableByParameterProperty { + private static readonly correctlyModifiableByParameterProperty = 7; + + public constructor( + public correctlyModifiablePublicParameter: number = (() => { + return TestModifiableStatic.correctlyModifiableByParameterProperty += 1; + })() + ) { } + }`, + `class TestReadonlyInline { + private readonly correctlyReadonlyInline = 7; + }`, + `class TestReadonlyDelayed { + private readonly correctlyReadonlyDelayed = 7; + + public constructor() { + this.correctlyReadonlyDelayed += 1; + } + }`, + `class TestModifiableInline { + private correctlyModifiableInline = 7; + + public mutate() { + this.correctlyModifiableInline += 1; + + return class { + private correctlyModifiableInline = 7; + + mutate() { + this.correctlyModifiableInline += 1; + } + }; + } + }`, + `class TestModifiableDelayed { + private correctlyModifiableDelayed = 7; + + public mutate() { + this.correctlyModifiableDelayed += 1; + } + }`, + `class TestModifiableDeleted { + private correctlyModifiableDeleted = 7; + + public mutate() { + delete this.correctlyModifiableDeleted; + } + }`, + `class TestModifiableWithinConstructor { + private correctlyModifiableWithinConstructor = 7; + + public constructor() { + (() => { + this.correctlyModifiableWithinConstructor += 1; + })(); + } + }`, + `class TestModifiableWithinConstructorArrowFunction { + private correctlyModifiableWithinConstructorArrowFunction = 7; + + public constructor() { + (() => { + this.correctlyModifiableWithinConstructorArrowFunction += 1; + })(); + } + }`, + `class TestModifiableWithinConstructorInFunctionExpression { + private correctlyModifiableWithinConstructorInFunctionExpression = 7; + + public constructor() { + const self = this; + + (() => { + self.correctlyModifiableWithinConstructorInFunctionExpression += 1; + })(); + } + }`, + `class TestModifiableWithinConstructorInGetAccessor { + private correctlyModifiableWithinConstructorInGetAccessor = 7; + + public constructor() { + const self = this; + + const confusingObject = { + get accessor() { + return self.correctlyModifiableWithinConstructorInGetAccessor += 1; + }, + }; + } + }`, + `class TestModifiableWithinConstructorInMethodDeclaration { + private correctlyModifiableWithinConstructorInMethodDeclaration = 7; + + public constructor() { + const self = this; + + const confusingObject = { + methodDeclaration() { + self.correctlyModifiableWithinConstructorInMethodDeclaration = 7; + } + }; + } + }`, + `class TestModifiableWithinConstructorInSetAccessor { + private correctlyModifiableWithinConstructorInSetAccessor = 7; + + public constructor() { + const self = this; + + const confusingObject = { + set accessor(value: number) { + self.correctlyModifiableWithinConstructorInSetAccessor += value; + }, + }; + } + }`, + `class TestModifiablePostDecremented { + private correctlyModifiablePostDecremented = 7; + + public mutate() { + this.correctlyModifiablePostDecremented -= 1; + } + }`, + `class TestyModifiablePostIncremented { + private correctlyModifiablePostIncremented = 7; + + public mutate() { + this.correctlyModifiablePostIncremented += 1; + } + }`, + `class TestModifiablePreDecremented { + private correctlyModifiablePreDecremented = 7; + + public mutate() { + --this.correctlyModifiablePreDecremented; + } + }`, + `class TestModifiablePreIncremented { + private correctlyModifiablePreIncremented = 7; + + public mutate() { + ++this.correctlyModifiablePreIncremented; + } + }`, + `class TestProtectedModifiable { + protected protectedModifiable = 7; + }`, + `class TestPublicModifiable { + public publicModifiable = 7; + }`, + `class TestReadonlyParameter { + public constructor( + private readonly correctlyReadonlyParameter = 7, + ) { } + }`, + `class TestCorrectlyModifiableParameter { + public constructor( + private correctlyModifiableParameter = 7, + ) { } + + public mutate() { + this.correctlyModifiableParameter += 1; + } + }`, + { + code: `class TestCorrectlyNonInlineLambdas { + private correctlyNonInlineLambda = 7; + }`, + options: [ + { + onlyInlineLambdas: true, + }, + ], + }, + ], + invalid: [ + { + code: `class TestIncorrectlyModifiableStatic { + private static incorrectlyModifiableStatic = 7; + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiableStatic', + }, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiableStatic { + private static readonly incorrectlyModifiableStatic = 7; + }`, + }, + { + code: `class TestIncorrectlyModifiableStaticArrow { + private static incorrectlyModifiableStaticArrow = () => 7; + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiableStaticArrow', + }, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiableStaticArrow { + private static readonly incorrectlyModifiableStaticArrow = () => 7; + }`, + }, + { + code: `class TestIncorrectlyModifiableInline { + private incorrectlyModifiableInline = 7; + + public createConfusingChildClass() { + return class { + private incorrectlyModifiableInline = 7; + } + } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiableInline', + }, + line: 2, + messageId: 'preferReadonly', + }, + { + data: { + name: 'incorrectlyModifiableInline', + }, + line: 6, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiableInline { + private readonly incorrectlyModifiableInline = 7; + + public createConfusingChildClass() { + return class { + private readonly incorrectlyModifiableInline = 7; + } + } + }`, + }, + { + code: `class TestIncorrectlyModifiableDelayed { + private incorrectlyModifiableDelayed = 7; + + public constructor() { + this.incorrectlyModifiableDelayed = 7; + } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiableDelayed', + }, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiableDelayed { + private readonly incorrectlyModifiableDelayed = 7; + + public constructor() { + this.incorrectlyModifiableDelayed = 7; + } + }`, + }, + { + code: `class TestChildClassExpressionModifiable { + private childClassExpressionModifiable = 7; + + public createConfusingChildClass() { + return class { + private childClassExpressionModifiable = 7; + + mutate() { + this.childClassExpressionModifiable += 1; + } + } + } + }`, + errors: [ + { + data: { + name: 'childClassExpressionModifiable', + }, + line: 2, + messageId: 'preferReadonly', + }, + ], + output: `class TestChildClassExpressionModifiable { + private readonly childClassExpressionModifiable = 7; + + public createConfusingChildClass() { + return class { + private childClassExpressionModifiable = 7; + + mutate() { + this.childClassExpressionModifiable += 1; + } + } + } + }`, + }, + { + code: `class TestIncorrectlyModifiablePostMinus { + private incorrectlyModifiablePostMinus = 7; + + public mutate() { + this.incorrectlyModifiablePostMinus - 1; + } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiablePostMinus', + }, + line: 2, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiablePostMinus { + private readonly incorrectlyModifiablePostMinus = 7; + + public mutate() { + this.incorrectlyModifiablePostMinus - 1; + } + }`, + }, + { + code: `class TestIncorrectlyModifiablePostPlus { + private incorrectlyModifiablePostPlus = 7; + + public mutate() { + this.incorrectlyModifiablePostPlus + 1; + } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiablePostPlus', + }, + line: 2, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiablePostPlus { + private readonly incorrectlyModifiablePostPlus = 7; + + public mutate() { + this.incorrectlyModifiablePostPlus + 1; + } + }`, + }, + { + code: `class TestIncorrectlyModifiablePreMinus { + private incorrectlyModifiablePreMinus = 7; + + public mutate() { + -this.incorrectlyModifiablePreMinus; + } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiablePreMinus', + }, + line: 2, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiablePreMinus { + private readonly incorrectlyModifiablePreMinus = 7; + + public mutate() { + -this.incorrectlyModifiablePreMinus; + } + }`, + }, + { + code: `class TestIncorrectlyModifiablePrePlus { + private incorrectlyModifiablePrePlus = 7; + + public mutate() { + +this.incorrectlyModifiablePrePlus; + } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiablePrePlus', + }, + line: 2, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiablePrePlus { + private readonly incorrectlyModifiablePrePlus = 7; + + public mutate() { + +this.incorrectlyModifiablePrePlus; + } + }`, + }, + { + code: `class TestOverlappingClassVariable { + private overlappingClassVariable = 7; + + public workWithSimilarClass(other: SimilarClass) { + other.overlappingClassVariable = 7; + } + } + + class SimilarClass { + public overlappingClassVariable = 7; + }`, + errors: [ + { + data: { + name: 'overlappingClassVariable', + }, + line: 2, + messageId: 'preferReadonly', + }, + ], + output: `class TestOverlappingClassVariable { + private readonly overlappingClassVariable = 7; + + public workWithSimilarClass(other: SimilarClass) { + other.overlappingClassVariable = 7; + } + } + + class SimilarClass { + public overlappingClassVariable = 7; + }`, + }, + { + code: `class TestIncorrectlyModifiableParameter { + public constructor( + private incorrectlyModifiableParameter = 7, + ) { } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiableParameter', + }, + line: 3, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiableParameter { + public constructor( + private readonly incorrectlyModifiableParameter = 7, + ) { } + }`, + }, + { + code: `class TestIncorrectlyModifiableParameter { + public constructor( + public ignore: boolean, + private incorrectlyModifiableParameter = 7, + ) { } + }`, + errors: [ + { + data: { + name: 'incorrectlyModifiableParameter', + }, + line: 4, + messageId: 'preferReadonly', + }, + ], + output: `class TestIncorrectlyModifiableParameter { + public constructor( + public ignore: boolean, + private readonly incorrectlyModifiableParameter = 7, + ) { } + }`, + }, + { + code: `class TestCorrectlyNonInlineLambdas { + private incorrectlyInlineLambda = () => 7; + }`, + errors: [ + { + data: { + name: 'incorrectlyInlineLambda', + }, + line: 2, + messageId: 'preferReadonly', + }, + ], + options: [ + { + onlyInlineLambdas: true, + }, + ], + output: `class TestCorrectlyNonInlineLambdas { + private readonly incorrectlyInlineLambda = () => 7; + }`, + }, + ], +}); diff --git a/packages/typescript-estree/src/convert.ts b/packages/typescript-estree/src/convert.ts index f8151e1b268c..cdf6b184150f 100644 --- a/packages/typescript-estree/src/convert.ts +++ b/packages/typescript-estree/src/convert.ts @@ -43,9 +43,9 @@ export function convertError(error: any) { export class Converter { private readonly ast: ts.SourceFile; - private options: ConverterOptions; - private esTreeNodeToTSNodeMap = new WeakMap(); - private tsNodeToESTreeNodeMap = new WeakMap(); + private readonly options: ConverterOptions; + private readonly esTreeNodeToTSNodeMap = new WeakMap(); + private readonly tsNodeToESTreeNodeMap = new WeakMap(); private allowPattern: boolean = false; private inTypeMode: boolean = false; From 606fc702d2d34b68c8e57dc21fe5eb7f23610c48 Mon Sep 17 00:00:00 2001 From: Moulik Aggarwal Date: Fri, 28 Jun 2019 10:24:54 +0530 Subject: [PATCH 02/13] feat(eslint-plugin): [no-explicit-any] Add an optional fixer (#609) --- packages/eslint-plugin/README.md | 2 +- .../docs/rules/no-explicit-any.md | 18 +++++++++++++ .../src/rules/no-explicit-any.ts | 26 ++++++++++++++++-- .../tests/rules/no-explicit-any.test.ts | 27 ++++++++++++++++--- 4 files changed, 67 insertions(+), 6 deletions(-) diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index d5163109b1e6..a6e9a3c60a2f 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -145,7 +145,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/no-empty-function`](./docs/rules/no-empty-function.md) | Disallow empty functions | | | | | [`@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-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type | :heavy_check_mark: | :wrench: | | | [`@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 | | | | | [`@typescript-eslint/no-floating-promises`](./docs/rules/no-floating-promises.md) | Requires Promise-like values to be handled appropriately. | | | :thought_balloon: | diff --git a/packages/eslint-plugin/docs/rules/no-explicit-any.md b/packages/eslint-plugin/docs/rules/no-explicit-any.md index 3e3a6391a6f8..ccec9e952313 100644 --- a/packages/eslint-plugin/docs/rules/no-explicit-any.md +++ b/packages/eslint-plugin/docs/rules/no-explicit-any.md @@ -87,6 +87,24 @@ function greet(param: Array): string {} function greet(param: Array): Array {} ``` +## Options + +The rule accepts an options object with the following properties: + +```ts +type Options = { + // if true, auto-fixing will be made available in which the "any" type is converted to an "unknown" type + fixToUnknown: boolean; + // specify if arrays from the rest operator are considered okay + ignoreRestArgs: boolean; +}; + +const defaults = { + fixToUnknown: false, + ignoreRestArgs: false, +}; +``` + ### ignoreRestArgs A boolean to specify if arrays from the rest operator are considered okay. `false` by default. diff --git a/packages/eslint-plugin/src/rules/no-explicit-any.ts b/packages/eslint-plugin/src/rules/no-explicit-any.ts index c27b4ab59923..f5a555933615 100644 --- a/packages/eslint-plugin/src/rules/no-explicit-any.ts +++ b/packages/eslint-plugin/src/rules/no-explicit-any.ts @@ -3,8 +3,17 @@ import { AST_NODE_TYPES, } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; -export default util.createRule({ +export type Options = [ + { + fixToUnknown?: boolean; + ignoreRestArgs?: boolean; + } +]; +export type MessageIds = 'unexpectedAny'; + +export default util.createRule({ name: 'no-explicit-any', meta: { type: 'suggestion', @@ -13,6 +22,7 @@ export default util.createRule({ category: 'Best Practices', recommended: 'warn', }, + fixable: 'code', messages: { unexpectedAny: 'Unexpected any. Specify a different type.', }, @@ -21,6 +31,9 @@ export default util.createRule({ type: 'object', additionalProperties: false, properties: { + fixToUnknown: { + type: 'boolean', + }, ignoreRestArgs: { type: 'boolean', }, @@ -30,10 +43,11 @@ export default util.createRule({ }, defaultOptions: [ { + fixToUnknown: false, ignoreRestArgs: false, }, ], - create(context, [{ ignoreRestArgs }]) { + create(context, [{ ignoreRestArgs, fixToUnknown }]) { /** * Checks if the node is an arrow function, function declaration or function expression * @param node the node to be validated. @@ -155,9 +169,17 @@ export default util.createRule({ if (ignoreRestArgs && isNodeDescendantOfRestElementInFunction(node)) { return; } + + let fix: TSESLint.ReportFixFunction | null = null; + + if (fixToUnknown) { + fix = fixer => fixer.replaceText(node, 'unknown'); + } + context.report({ node, messageId: 'unexpectedAny', + fix, }); }, }; diff --git a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts index cf596ffecbc3..b27cc8111bc0 100644 --- a/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts +++ b/packages/eslint-plugin/tests/rules/no-explicit-any.test.ts @@ -1,5 +1,8 @@ -import rule from '../../src/rules/no-explicit-any'; +import rule, { MessageIds, Options } from '../../src/rules/no-explicit-any'; import { RuleTester } from '../RuleTester'; +import { TSESLint } from '@typescript-eslint/experimental-utils'; + +type InvalidTestCase = TSESLint.InvalidTestCase; const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', @@ -187,7 +190,7 @@ type obj = { options: [{ ignoreRestArgs: true }], }, ], - invalid: [ + invalid: ([ { code: 'const number: any = 1', errors: [ @@ -784,5 +787,23 @@ type obj = { }, ], }, - ], + ] as InvalidTestCase[]).reduce((acc, testCase) => { + acc.push(testCase); + const options = testCase.options || []; + const code = `// fixToUnknown: true\n${testCase.code}`; + acc.push({ + code, + output: code.replace(/any/g, 'unknown'), + options: [{ ...options[0], fixToUnknown: true }], + errors: testCase.errors.map(err => { + if (err.line === undefined) { + return err; + } + + return { ...err, line: err.line + 1 }; + }), + }); + + return acc; + }, []), }); From e325b728098f73b98ada8ec05ea0175403c80d1f Mon Sep 17 00:00:00 2001 From: golopot Date: Fri, 28 Jun 2019 23:10:06 +0800 Subject: [PATCH 03/13] feat(eslint-plugin): [ban-types] Support namespaced type (#616) --- packages/eslint-plugin/src/rules/ban-types.ts | 77 +++++++++--------- .../tests/rules/ban-types.test.ts | 78 +++++++++++++++++++ 2 files changed, 119 insertions(+), 36 deletions(-) diff --git a/packages/eslint-plugin/src/rules/ban-types.ts b/packages/eslint-plugin/src/rules/ban-types.ts index 059f73020249..d0a57daf2335 100644 --- a/packages/eslint-plugin/src/rules/ban-types.ts +++ b/packages/eslint-plugin/src/rules/ban-types.ts @@ -1,8 +1,4 @@ -import { - TSESLint, - TSESTree, - AST_NODE_TYPES, -} from '@typescript-eslint/experimental-utils'; +import { TSESLint, TSESTree } from '@typescript-eslint/experimental-utils'; import * as util from '../util'; type Options = [ @@ -20,6 +16,31 @@ type Options = [ ]; type MessageIds = 'bannedTypeMessage'; +function stringifyTypeName( + node: TSESTree.EntityName, + sourceCode: TSESLint.SourceCode, +): string { + return sourceCode.getText(node).replace(/ /g, ''); +} + +function getCustomMessage( + bannedType: null | string | { message?: string; fixWith?: string }, +) { + if (bannedType === null) { + return ''; + } + + if (typeof bannedType === 'string') { + return ` ${bannedType}`; + } + + if (bannedType.message) { + return ` ${bannedType.message}`; + } + + return ''; +} + export default util.createRule({ name: 'ban-types', meta: { @@ -87,39 +108,23 @@ export default util.createRule({ ], create(context, [{ types: bannedTypes }]) { return { - 'TSTypeReference Identifier'(node: TSESTree.Identifier) { - if ( - node.parent && - node.parent.type !== AST_NODE_TYPES.TSQualifiedName - ) { - if (node.name in bannedTypes) { - let customMessage = ''; - const bannedCfgValue = bannedTypes[node.name]; + TSTypeReference({ typeName }) { + const name = stringifyTypeName(typeName, context.getSourceCode()); - let fix: TSESLint.ReportFixFunction | null = null; + if (name in bannedTypes) { + const bannedType = bannedTypes[name]; + const customMessage = getCustomMessage(bannedType); + const fixWith = bannedType && (bannedType as any).fixWith; - if (typeof bannedCfgValue === 'string') { - customMessage += ` ${bannedCfgValue}`; - } else if (bannedCfgValue !== null) { - if (bannedCfgValue.message) { - customMessage += ` ${bannedCfgValue.message}`; - } - if (bannedCfgValue.fixWith) { - const fixWith = bannedCfgValue.fixWith; - fix = fixer => fixer.replaceText(node, fixWith); - } - } - - context.report({ - node, - messageId: 'bannedTypeMessage', - data: { - name: node.name, - customMessage, - }, - fix, - }); - } + context.report({ + node: typeName, + messageId: 'bannedTypeMessage', + data: { + name: name, + customMessage, + }, + fix: fixWith ? fixer => fixer.replaceText(typeName, fixWith) : null, + }); } }, }; diff --git a/packages/eslint-plugin/tests/rules/ban-types.test.ts b/packages/eslint-plugin/tests/rules/ban-types.test.ts index 12766b0438a0..73427fcc8064 100644 --- a/packages/eslint-plugin/tests/rules/ban-types.test.ts +++ b/packages/eslint-plugin/tests/rules/ban-types.test.ts @@ -16,6 +16,10 @@ const options: InferOptionsTypeFromRule = [ Object: "Use '{}' instead.", Array: null, F: null, + 'NS.Bad': { + message: 'Use NS.Good instead.', + fixWith: 'NS.Good', + }, }, }, ]; @@ -39,6 +43,14 @@ ruleTester.run('ban-types', rule, { code: 'let e: foo.String;', options, }, + { + code: 'let a: _.NS.Bad', + options, + }, + { + code: 'let a: NS.Bad._', + options, + }, ], invalid: [ { @@ -56,6 +68,25 @@ ruleTester.run('ban-types', rule, { ], options, }, + { + code: 'let aa: Foo;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'Foo', + customMessage: '', + }, + }, + ], + options: [ + { + types: { + Foo: { message: '' }, + }, + }, + ], + }, { code: 'let b: {c: String};', output: 'let b: {c: string};', @@ -217,5 +248,52 @@ class Foo extends Bar implements Baz { ], options, }, + { + code: 'let a: NS.Bad;', + output: 'let a: NS.Good;', + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'NS.Bad', + customMessage: ' Use NS.Good instead.', + }, + line: 1, + column: 8, + }, + ], + options, + }, + { + code: ` +let a: NS.Bad; +let b: Foo; + `, + output: ` +let a: NS.Good; +let b: Foo; + `, + errors: [ + { + messageId: 'bannedTypeMessage', + data: { + name: 'NS.Bad', + customMessage: ' Use NS.Good instead.', + }, + line: 2, + column: 8, + }, + { + messageId: 'bannedTypeMessage', + data: { + name: 'NS.Bad', + customMessage: ' Use NS.Good instead.', + }, + line: 3, + column: 12, + }, + ], + options, + }, ], }); From 430d628b0271718e2df470831832fa40b41150be Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Fri, 28 Jun 2019 08:32:19 -0700 Subject: [PATCH 04/13] fix(eslint-plugin): handle `const;` (#633) Fixes #441 --- .../src/rules/consistent-type-definitions.ts | 1 - .../src/rules/indent-new-do-not-use/index.ts | 4 ++++ packages/eslint-plugin/src/rules/indent.ts | 9 +++++++++ packages/eslint-plugin/src/rules/prefer-for-of.ts | 5 ++++- packages/eslint-plugin/tests/rules/indent/indent.test.ts | 2 ++ packages/typescript-estree/src/ts-estree/ts-estree.ts | 1 + 6 files changed, 20 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts index d8d9bb675a21..eea12b9cf845 100644 --- a/packages/eslint-plugin/src/rules/consistent-type-definitions.ts +++ b/packages/eslint-plugin/src/rules/consistent-type-definitions.ts @@ -27,7 +27,6 @@ export default util.createRule({ const sourceCode = context.getSourceCode(); return { - // VariableDeclaration with kind type has only one VariableDeclarator "TSTypeAliasDeclaration[typeAnnotation.type='TSTypeLiteral']"( node: TSESTree.TSTypeAliasDeclaration, ) { 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 14088c553789..70cf1c37919a 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 @@ -1382,6 +1382,10 @@ export default createRule({ }, VariableDeclaration(node) { + if (node.declarations.length === 0) { + return; + } + let variableIndent = Object.prototype.hasOwnProperty.call( options.VariableDeclarator, node.kind, diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts index 6b2a83066dd4..321f32f4cceb 100644 --- a/packages/eslint-plugin/src/rules/indent.ts +++ b/packages/eslint-plugin/src/rules/indent.ts @@ -175,6 +175,15 @@ export default util.createRule({ } }, + VariableDeclaration(node: TSESTree.VariableDeclaration) { + // https://github.com/typescript-eslint/typescript-eslint/issues/441 + if (node.declarations.length === 0) { + return; + } + + return rules.VariableDeclaration(node); + }, + TSAsExpression(node: TSESTree.TSAsExpression) { // transform it to a BinaryExpression return rules['BinaryExpression, LogicalExpression']({ diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts index 19551a911664..89f6c5403032 100644 --- a/packages/eslint-plugin/src/rules/prefer-for-of.ts +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -186,8 +186,11 @@ export default util.createRule({ return; } - const [declarator] = node.init.declarations; + const declarator = node.init.declarations[0] as + | TSESTree.VariableDeclarator + | undefined; if ( + !declarator || !isZeroInitialized(declarator) || declarator.id.type !== AST_NODE_TYPES.Identifier ) { diff --git a/packages/eslint-plugin/tests/rules/indent/indent.test.ts b/packages/eslint-plugin/tests/rules/indent/indent.test.ts index e992b440c6cc..9fb9ef7dadd6 100644 --- a/packages/eslint-plugin/tests/rules/indent/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent/indent.test.ts @@ -768,6 +768,8 @@ const div: JQuery = $('
') `, options: [2, { VariableDeclarator: { const: 3 } }], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/441 + `const;`, ], invalid: [ ...individualNodeTests.invalid, diff --git a/packages/typescript-estree/src/ts-estree/ts-estree.ts b/packages/typescript-estree/src/ts-estree/ts-estree.ts index e786e83e0dfb..8eed48390d0f 100644 --- a/packages/typescript-estree/src/ts-estree/ts-estree.ts +++ b/packages/typescript-estree/src/ts-estree/ts-estree.ts @@ -1387,6 +1387,7 @@ export interface UnaryExpression extends UnaryExpressionBase { export interface VariableDeclaration extends BaseNode { type: AST_NODE_TYPES.VariableDeclaration; + // NOTE - this is not guaranteed to have any elements in it. i.e. `const;` declarations: VariableDeclarator[]; kind: 'let' | 'const' | 'var'; declare?: boolean; From b83ff5a8944d973339ae7dd93c9fcc531c2b86eb Mon Sep 17 00:00:00 2001 From: Trivikram Kamat <16024985+trivikr@users.noreply.github.com> Date: Fri, 28 Jun 2019 08:34:55 -0700 Subject: [PATCH 05/13] docs: add link to eslint support in TS repo (#650) --- README.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/README.md b/README.md index d8bc576a1ef5..6900259d2784 100644 --- a/README.md +++ b/README.md @@ -58,7 +58,7 @@ One advantage is there is no tooling required to reconcile differences between A Palantir, the backers behind TSLint announced earlier this year that **they would be deprecating TSLint in favor of supporting `typescript-eslint`** in order to benefit the community. You can read more about that here: https://medium.com/palantir/tslint-in-2019-1a144c2317a9 -The TypeScript Team themselves also announced their plans to move the TypeScript codebase from TSLint to `typescript-eslint`, and they have been big supporters of this project. +The TypeScript Team themselves also announced their plans to move the TypeScript codebase from TSLint to `typescript-eslint`, and they have been big supporters of this project. More details at https://github.com/microsoft/TypeScript/issues/30553
From 44b099df8a5fc95150673460cbe38c4304779f82 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Fri, 28 Jun 2019 08:37:18 -0700 Subject: [PATCH 06/13] docs(eslint-plugin): Expand docs for array-type (#634) I noticed the docs were pretty sparse. Simple, but not hugely helpful. I just expanded them with examples --- .../eslint-plugin/docs/rules/array-type.md | 88 +++++++++++++++---- 1 file changed, 69 insertions(+), 19 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/array-type.md b/packages/eslint-plugin/docs/rules/array-type.md index 448d13ad3bcd..60a8d612d063 100644 --- a/packages/eslint-plugin/docs/rules/array-type.md +++ b/packages/eslint-plugin/docs/rules/array-type.md @@ -1,33 +1,83 @@ # Requires using either `T[]` or `Array` for arrays (array-type) -```ts -class Foo>> extends Bar> - implements Baz> { - private s: Array; - - constructor(p: Array) { - return new Array(); - } -} -``` +Using the same style for array definitions across your codebase makes it easier for your developers to read and understand the types. ## Rule Details -This rule aims to standardise usage of array. +This rule aims to standardise usage of array types within your codebase. ## Options -Default config: +This rule accepts one option - a single string + +- `"array"` enforces use of `T[]` for all types `T`. +- `"generic"` enforces use of `Array` for all types `T`. +- `"array-simple"` enforces use of `T[]` if `T` is a simple type. + +Without providing an option, by default the rule will enforce `"array"`. + +### `"array"` + +Always use `T[]` or `readonly T[]` for all array types. + +Incorrect code for `"array"`: + +```ts +const x: Array = ["a", "b"]; +const y: ReadonlyArray = ["a", "b"]; +``` -```JSON -{ - "array-type": ["error", "array"] -} +Correct code for `"array"`: + +```ts +const x: string[] = ["a", "b"]; +const y: readonly string[] = ["a", "b"]; ``` -- `array` enforces use of `T[]` for all types `T`. -- `generic` enforces use of `Array` for all types `T`. -- `array-simple` enforces use of `T[]` if `T` is a simple type. +### `"generic"` + +Always use `Array` or `ReadonlyArray` for all array types. + +Incorrect code for `"generic"`: + +```ts +const x: string[] = ["a", "b"]; +const y: readonly string[] = ["a", "b"]; +``` + +Correct code for `"generic"`: + +```ts +const x: Array = ["a", "b"]; +const y: ReadonlyArray = ["a", "b"]; +``` + +### `"array-simple"` + +Use `T[]` or `readonly T[]` for simple types (i.e. types which are just primitive names or type references). +Use `Array` or `ReadonlyArray` for all other types (union types, intersection types, object types, function types, etc). + +Incorrect code for `"array-simple"`: + +```ts +const a: (string | number)[] = ["a", "b"]; +const b: ({ prop: string })[] = [{ prop: "a" }]; +const c: (() => void)[] = [() => {}]; +const d: Array = ["a", "b"]; +const e: Array = ["a", "b"]; +const f: ReadonlyArray = ["a", "b"]; +``` + +Correct code for `"array-simple"`: + +```ts +const a: Array = ["a", "b"]; +const b: Array<{ prop: string }> = [{ prop: "a" }]; +const c: Array<() => void> = [() => {}]; +const d: MyType[] = ["a", "b"]; +const e: string[] = ["a", "b"]; +const f: readonly string[] = ["a", "b"]; +``` ## Related to From 44677b4f139d6a8681cce8c289c8f6733aa53366 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Fri, 28 Jun 2019 16:48:42 -0700 Subject: [PATCH 07/13] docs(eslint-plugin): format array-type docs --- .../eslint-plugin/docs/rules/array-type.md | 36 +++++++++---------- 1 file changed, 18 insertions(+), 18 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/array-type.md b/packages/eslint-plugin/docs/rules/array-type.md index 60a8d612d063..8906f0c9a4c2 100644 --- a/packages/eslint-plugin/docs/rules/array-type.md +++ b/packages/eslint-plugin/docs/rules/array-type.md @@ -23,15 +23,15 @@ Always use `T[]` or `readonly T[]` for all array types. Incorrect code for `"array"`: ```ts -const x: Array = ["a", "b"]; -const y: ReadonlyArray = ["a", "b"]; +const x: Array = ['a', 'b']; +const y: ReadonlyArray = ['a', 'b']; ``` Correct code for `"array"`: ```ts -const x: string[] = ["a", "b"]; -const y: readonly string[] = ["a", "b"]; +const x: string[] = ['a', 'b']; +const y: readonly string[] = ['a', 'b']; ``` ### `"generic"` @@ -41,15 +41,15 @@ Always use `Array` or `ReadonlyArray` for all array types. Incorrect code for `"generic"`: ```ts -const x: string[] = ["a", "b"]; -const y: readonly string[] = ["a", "b"]; +const x: string[] = ['a', 'b']; +const y: readonly string[] = ['a', 'b']; ``` Correct code for `"generic"`: ```ts -const x: Array = ["a", "b"]; -const y: ReadonlyArray = ["a", "b"]; +const x: Array = ['a', 'b']; +const y: ReadonlyArray = ['a', 'b']; ``` ### `"array-simple"` @@ -60,23 +60,23 @@ Use `Array` or `ReadonlyArray` for all other types (union types, intersect Incorrect code for `"array-simple"`: ```ts -const a: (string | number)[] = ["a", "b"]; -const b: ({ prop: string })[] = [{ prop: "a" }]; +const a: (string | number)[] = ['a', 'b']; +const b: ({ prop: string })[] = [{ prop: 'a' }]; const c: (() => void)[] = [() => {}]; -const d: Array = ["a", "b"]; -const e: Array = ["a", "b"]; -const f: ReadonlyArray = ["a", "b"]; +const d: Array = ['a', 'b']; +const e: Array = ['a', 'b']; +const f: ReadonlyArray = ['a', 'b']; ``` Correct code for `"array-simple"`: ```ts -const a: Array = ["a", "b"]; -const b: Array<{ prop: string }> = [{ prop: "a" }]; +const a: Array = ['a', 'b']; +const b: Array<{ prop: string }> = [{ prop: 'a' }]; const c: Array<() => void> = [() => {}]; -const d: MyType[] = ["a", "b"]; -const e: string[] = ["a", "b"]; -const f: readonly string[] = ["a", "b"]; +const d: MyType[] = ['a', 'b']; +const e: string[] = ['a', 'b']; +const f: readonly string[] = ['a', 'b']; ``` ## Related to From 34e7d1ea4d4f7ee9e71c07d20fad711e4dd29d6c Mon Sep 17 00:00:00 2001 From: Jonathan Delgado Date: Sun, 30 Jun 2019 21:51:34 -0500 Subject: [PATCH 08/13] feat(eslint-plugin): add rule strict-boolean-expressions (#579) --- packages/eslint-plugin/README.md | 1 + packages/eslint-plugin/ROADMAP.md | 2 +- .../docs/rules/strict-boolean-expressions.md | 57 ++ packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/strict-boolean-expressions.ts | 101 ++ .../rules/strict-boolean-expressions.test.ts | 907 ++++++++++++++++++ 6 files changed, 1069 insertions(+), 1 deletion(-) create mode 100644 packages/eslint-plugin/docs/rules/strict-boolean-expressions.md create mode 100644 packages/eslint-plugin/src/rules/strict-boolean-expressions.ts create mode 100644 packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index a6e9a3c60a2f..875f89bbebe0 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -178,6 +178,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@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/strict-boolean-expressions`](./docs/rules/strict-boolean-expressions.md) | Restricts the types allowed in boolean expressions | | | :thought_balloon: | | [`@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/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index 8a527cd33132..60cdbe7de24f 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -92,7 +92,7 @@ | [`prefer-object-spread`] | 🌟 | [`prefer-object-spread`][prefer-object-spread] | | [`radix`] | 🌟 | [`radix`][radix] | | [`restrict-plus-operands`] | ✅ | [`@typescript-eslint/restrict-plus-operands`] | -| [`strict-boolean-expressions`] | 🛑 | N/A | +| [`strict-boolean-expressions`] | ✅ | [`@typescript-eslint/strict-boolean-expressions`] | | [`strict-type-predicates`] | 🛑 | N/A | | [`switch-default`] | 🌟 | [`default-case`][default-case] | | [`triple-equals`] | 🌟 | [`eqeqeq`][eqeqeq] | diff --git a/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md new file mode 100644 index 000000000000..699af9baa431 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/strict-boolean-expressions.md @@ -0,0 +1,57 @@ +# Boolean expressions are limited to booleans (strict-boolean-expressions) + +Requires that any boolean expression is limited to true booleans rather than +casting another primitive to a boolean at runtime. + +It is useful to be explicit, for example, if you were trying to check if a +number was defined. Doing `if (number)` would evaluate to `false` if `number` +was defined and `0`. This rule forces these expressions to be explicit and to +strictly use booleans. + +The following nodes are checked: + +- Arguments to the `!`, `&&`, and `||` operators +- The condition in a conditional expression `(cond ? x : y)` +- Conditions for `if`, `for`, `while`, and `do-while` statements. + +Examples of **incorrect** code for this rule: + +```ts +const number = 0; +if (number) { + return; +} + +let foo = bar || 'foobar'; + +let undefinedItem; +let foo = undefinedItem ? 'foo' : 'bar'; + +let str = 'foo'; +while (str) { + break; +} +``` + +Examples of **correct** code for this rule: + +```ts +const number = 0; +if (typeof number !== 'undefined') { + return; +} + +let foo = typeof bar !== 'undefined' ? bar : 'foobar'; + +let undefinedItem; +let foo = typeof undefinedItem !== 'undefined' ? 'foo' : 'bar'; + +let str = 'foo'; +while (typeof str !== 'undefined') { + break; +} +``` + +## Related To + +- TSLint: [strict-boolean-expressions](https://palantir.github.io/tslint/rules/strict-boolean-expressions) diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index 5d6fc99aa486..f93cb788cf7d 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -53,6 +53,7 @@ import promiseFunctionAsync from './promise-function-async'; import requireArraySortCompare from './require-array-sort-compare'; import restrictPlusOperands from './restrict-plus-operands'; import semi from './semi'; +import strictBooleanExpressions from './strict-boolean-expressions'; import typeAnnotationSpacing from './type-annotation-spacing'; import unboundMethod from './unbound-method'; import unifiedSignatures from './unified-signatures'; @@ -113,6 +114,7 @@ export default { 'require-array-sort-compare': requireArraySortCompare, 'restrict-plus-operands': restrictPlusOperands, semi: semi, + 'strict-boolean-expressions': strictBooleanExpressions, 'type-annotation-spacing': typeAnnotationSpacing, 'unbound-method': unboundMethod, 'unified-signatures': unifiedSignatures, diff --git a/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts new file mode 100644 index 000000000000..5118b46a2486 --- /dev/null +++ b/packages/eslint-plugin/src/rules/strict-boolean-expressions.ts @@ -0,0 +1,101 @@ +import { + TSESTree, + AST_NODE_TYPES, +} from '@typescript-eslint/experimental-utils'; +import ts from 'typescript'; +import * as tsutils from 'tsutils'; +import * as util from '../util'; + +type ExpressionWithTest = + | TSESTree.ConditionalExpression + | TSESTree.DoWhileStatement + | TSESTree.ForStatement + | TSESTree.IfStatement + | TSESTree.WhileStatement; + +export default util.createRule({ + name: 'strict-boolean-expressions', + meta: { + type: 'suggestion', + docs: { + description: 'Restricts the types allowed in boolean expressions', + category: 'Best Practices', + recommended: false, + }, + schema: [], + messages: { + strictBooleanExpression: 'Unexpected non-boolean in conditional.', + }, + }, + defaultOptions: [], + create(context) { + const service = util.getParserServices(context); + const checker = service.program.getTypeChecker(); + + /** + * Determines if the node has a boolean type. + */ + function isBooleanType(node: TSESTree.Node): boolean { + const tsNode = service.esTreeNodeToTSNodeMap.get( + node, + ); + const type = util.getConstrainedTypeAtLocation(checker, tsNode); + return tsutils.isTypeFlagSet(type, ts.TypeFlags.BooleanLike); + } + + /** + * Asserts that a testable expression contains a boolean, reports otherwise. + * Filters all LogicalExpressions to prevent some duplicate reports. + */ + function assertTestExpressionContainsBoolean( + node: ExpressionWithTest, + ): void { + if ( + node.test !== null && + node.test.type !== AST_NODE_TYPES.LogicalExpression && + !isBooleanType(node.test) + ) { + reportNode(node.test); + } + } + + /** + * Asserts that a logical expression contains a boolean, reports otherwise. + */ + function assertLocalExpressionContainsBoolean( + node: TSESTree.LogicalExpression, + ): void { + if (!isBooleanType(node.left) || !isBooleanType(node.right)) { + reportNode(node); + } + } + + /** + * Asserts that a unary expression contains a boolean, reports otherwise. + */ + function assertUnaryExpressionContainsBoolean( + node: TSESTree.UnaryExpression, + ): void { + if (!isBooleanType(node.argument)) { + reportNode(node.argument); + } + } + + /** + * Reports an offending node in context. + */ + function reportNode(node: TSESTree.Node): void { + context.report({ node, messageId: 'strictBooleanExpression' }); + } + + return { + ConditionalExpression: assertTestExpressionContainsBoolean, + DoWhileStatement: assertTestExpressionContainsBoolean, + ForStatement: assertTestExpressionContainsBoolean, + IfStatement: assertTestExpressionContainsBoolean, + WhileStatement: assertTestExpressionContainsBoolean, + LogicalExpression: assertLocalExpressionContainsBoolean, + 'UnaryExpression[operator="!"]': assertUnaryExpressionContainsBoolean, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts new file mode 100644 index 000000000000..030bade0f480 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/strict-boolean-expressions.test.ts @@ -0,0 +1,907 @@ +import path from 'path'; +import rule from '../../src/rules/strict-boolean-expressions'; +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('strict-boolean-expressions', rule, { + valid: [ + ` + let val = true; + let bool = !val; + let bool2 = true || val; + let bool3 = true && val; + `, + ` + let a = 0; + let u1 = typeof a; + let u2 = -a; + let u3 = ~a; + `, + ` + const bool1 = true; + const bool2 = false; + if (true) { + return; + } + + if (bool1) { + return; + } + + if (bool1 && bool2) { + return; + } + + if (bool1 || bool2) { + return; + } + + if ((bool1 && bool2) || (bool1 || bool2)) { + return; + } + `, + ` + const bool1 = true; + const bool2 = false; + const res1 = true ? true : false; + const res2 = bool1 && bool2 ? true : false; + const res3 = bool1 || bool2 ? true : false; + const res4 = (bool1 && bool2) || (bool1 || bool2) ? true : false; + `, + ` + for (let i = 0; true; i++) { + break; + } + `, + ` + const bool = true; + for (let i = 0; bool; i++) { + break; + } + `, + ` + const bool1 = true; + const bool2 = false; + for (let i = 0; bool1 && bool2; i++) { + break; + } + `, + ` + const bool1 = true; + const bool2 = false; + for (let i = 0; bool1 || bool2; i++) { + break; + } + `, + ` + const bool1 = true; + const bool2 = false; + for (let i = 0; (bool1 && bool2) || (bool1 || bool2); i++) { + break; + } + `, + ` + while (true) { + break; + } + `, + ` + const bool = true; + while (bool) { + break; + } + `, + ` + const bool1 = true; + const bool2 = false; + while (bool1 && bool2) { + break; + } + `, + ` + const bool1 = true; + const bool2 = false; + while (bool1 || bool2) { + break; + } + `, + ` + const bool1 = true; + const bool2 = false; + while ((bool1 && bool2) || (bool1 || bool2)) { + break; + } + `, + ` + do { + break; + } while (true); + `, + ` + const bool = true; + do { + break; + } while (bool); + `, + ` + const bool1 = true; + const bool2 = false; + do { + break; + } while (bool1 && bool2); + `, + ` + const bool1 = true; + const bool2 = false; + do { + break; + } while (bool1 || bool2); + `, + ` + const bool1 = true; + const bool2 = false; + do { + break; + } while ((bool1 && bool2) || (bool1 || bool2)); + `, + ` + function foo(arg: T) { return !arg; } + `, + ], + + invalid: [ + { + code: ` + let val = 1; + let bool = !val; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 21, + }, + ], + }, + { + code: ` + let val; + let bool = !val; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 21, + }, + ], + }, + { + code: ` + let val = 1; + let bool = true && val; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 20, + }, + ], + }, + { + code: ` + let val; + let bool = true && val; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 20, + }, + ], + }, + { + code: ` + let val = 1; + let bool = true || val; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 20, + }, + ], + }, + { + code: ` + let val; + let bool = true || val; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 20, + }, + ], + }, + { + code: ` + if (1) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 13, + }, + ], + }, + { + code: ` + if (undefined) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 13, + }, + ], + }, + { + code: ` + let item = 1; + if (item) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 13, + }, + ], + }, + { + code: ` + let item; + if (item) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 13, + }, + ], + }, + { + code: ` + let item1 = true; + let item2 = 1; + if (item1 && item2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 13, + }, + ], + }, + { + code: ` + let item1 = 1; + let item2 = true; + if (item1 && item2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 13, + }, + ], + }, + { + code: ` + let item1; + let item2 = true; + if (item1 && item2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 13, + }, + ], + }, + { + code: ` + let item1 = true; + let item2 = 1; + if (item1 || item2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 13, + }, + ], + }, + { + code: ` + let item1 = 1; + let item2 = true; + if (item1 || item2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 13, + }, + ], + }, + { + code: ` + let item1; + let item2 = true; + if (item1 || item2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 13, + }, + ], + }, + { + code: ` + const bool = 1 ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 22, + }, + ], + }, + { + code: ` + const bool = undefined ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 22, + }, + ], + }, + { + code: ` + let item = 1; + const bool = item ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 22, + }, + ], + }, + { + code: ` + let item; + const bool = item ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 22, + }, + ], + }, + { + code: ` + let item1 = 1; + let item2 = false; + const bool = item1 && item2 ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 22, + }, + ], + }, + { + code: ` + let item1 = true; + let item2 = 1; + const bool = item1 && item2 ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 22, + }, + ], + }, + { + code: ` + let item1 = true; + let item2; + const bool = item1 && item2 ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 22, + }, + ], + }, + { + code: ` + let item1 = 1; + let item2 = false; + const bool = item1 || item2 ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 22, + }, + ], + }, + { + code: ` + let item1 = true; + let item2 = 1; + const bool = item1 || item2 ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 22, + }, + ], + }, + { + code: ` + let item1 = true; + let item2; + const bool = item1 || item2 ? true : false; + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 22, + }, + ], + }, + { + code: ` + for (let i = 0; 1; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 25, + }, + ], + }, + { + code: ` + for (let i = 0; undefined; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 25, + }, + ], + }, + { + code: ` + let bool = 1; + for (let i = 0; bool; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 25, + }, + ], + }, + { + code: ` + let bool; + for (let i = 0; bool; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 25, + }, + ], + }, + { + code: ` + let bool1 = 1; + let bool2 = true; + for (let i = 0; bool1 && bool2; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 25, + }, + ], + }, + { + code: ` + let bool1; + let bool2 = true; + for (let i = 0; bool1 && bool2; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 25, + }, + ], + }, + { + code: ` + let bool1 = 1; + let bool2 = true; + for (let i = 0; bool1 || bool2; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 25, + }, + ], + }, + { + code: ` + let bool1; + let bool2 = true; + for (let i = 0; bool1 || bool2; i++) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 25, + }, + ], + }, + { + code: ` + while (1) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 16, + }, + ], + }, + { + code: ` + while (undefined) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 16, + }, + ], + }, + { + code: ` + let bool = 1; + while (bool) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 16, + }, + ], + }, + { + code: ` + let bool; + while (bool) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 3, + column: 16, + }, + ], + }, + { + code: ` + let bool1 = 1; + let bool2 = true; + while (bool1 && bool2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 16, + }, + ], + }, + { + code: ` + let bool1; + let bool2 = true; + while (bool1 && bool2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 16, + }, + ], + }, + { + code: ` + let bool1 = 1; + let bool2 = true; + while (bool1 || bool2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 16, + }, + ], + }, + { + code: ` + let bool1; + let bool2 = true; + while (bool1 || bool2) { + return; + } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 16, + }, + ], + }, + { + code: ` + do { + return; + } while (1); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 18, + }, + ], + }, + { + code: ` + do { + return; + } while (undefined); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 4, + column: 18, + }, + ], + }, + { + code: ` + let bool = 1; + do { + return; + } while (bool); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 5, + column: 18, + }, + ], + }, + { + code: ` + let bool; + do { + return; + } while (bool); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 5, + column: 18, + }, + ], + }, + { + code: ` + let bool1 = 1; + let bool2 = true; + do { + return; + } while (bool1 && bool2); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 6, + column: 18, + }, + ], + }, + { + code: ` + let bool1; + let bool2 = true; + do { + return; + } while (bool1 && bool2); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 6, + column: 18, + }, + ], + }, + { + code: ` + let bool1 = 1; + let bool2 = true; + do { + return; + } while (bool1 || bool2); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 6, + column: 18, + }, + ], + }, + { + code: ` + let bool1; + let bool2 = true; + do { + return; + } while (bool1 || bool2); + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 6, + column: 18, + }, + ], + }, + { + code: ` + function foo(arg: T) { return !arg; } + `, + errors: [ + { + messageId: 'strictBooleanExpression', + line: 2, + column: 58, + }, + ], + }, + ], +}); From af70a5932f8032cde13f4a0c98d6657cdb40b441 Mon Sep 17 00:00:00 2001 From: Jesse Trinity <42591254+jessetrinity@users.noreply.github.com> Date: Mon, 1 Jul 2019 18:52:02 -0700 Subject: [PATCH 09/13] feat(eslint-plugin): Add rule no-reference-import (#625) * feat(eslint-plugin): added no-reference-import rule * docs: added docs for no-reference-import * fix(eslint-plugin): collect references when visiting the program node * feat(eslint-plugin): updated rule to cover more reference directives * fix(eslint-plugin): deprecated rule and added coverage * Update README.md --- packages/eslint-plugin/README.md | 2 +- .../docs/rules/no-triple-slash-reference.md | 2 + .../docs/rules/triple-slash-reference.md | 58 ++++++++ packages/eslint-plugin/src/configs/all.json | 2 +- packages/eslint-plugin/src/rules/index.ts | 2 + .../src/rules/no-triple-slash-reference.ts | 6 +- .../src/rules/triple-slash-reference.ts | 129 ++++++++++++++++++ .../rules/no-triple-slash-reference.test.ts | 4 +- .../rules/triple-slash-reference.test.ts | 128 +++++++++++++++++ 9 files changed, 327 insertions(+), 6 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/triple-slash-reference.md create mode 100644 packages/eslint-plugin/src/rules/triple-slash-reference.ts create mode 100644 packages/eslint-plugin/tests/rules/triple-slash-reference.test.ts diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 875f89bbebe0..c436e32be6c1 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -159,7 +159,6 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@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: | @@ -179,6 +178,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 | | | :thought_balloon: | | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | | [`@typescript-eslint/strict-boolean-expressions`](./docs/rules/strict-boolean-expressions.md) | Restricts the types allowed in boolean expressions | | | :thought_balloon: | +| [`@typescript-eslint/triple-slash-reference`](./docs/rules/triple-slash-reference.md) | Sets preference level for triple slash directives versus ES6-style import declarations | | | | | [`@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/docs/rules/no-triple-slash-reference.md b/packages/eslint-plugin/docs/rules/no-triple-slash-reference.md index be090acea265..409ff6093ca1 100644 --- a/packages/eslint-plugin/docs/rules/no-triple-slash-reference.md +++ b/packages/eslint-plugin/docs/rules/no-triple-slash-reference.md @@ -12,6 +12,8 @@ A triple-slash reference directive is a comment beginning with three slashes fol ES6 Modules handle this now: `import animal from "./Animal"` +## DEPRECATED - this rule has been deprecated in favour of [`triple-slash-reference`](./triple-slash-reference.md) + ## Rule Details Does not allow the use of `/// ` comments. diff --git a/packages/eslint-plugin/docs/rules/triple-slash-reference.md b/packages/eslint-plugin/docs/rules/triple-slash-reference.md new file mode 100644 index 000000000000..3dccaa925e9b --- /dev/null +++ b/packages/eslint-plugin/docs/rules/triple-slash-reference.md @@ -0,0 +1,58 @@ +# Sets preference level for triple slash directives versus ES6-style import declarations. (triple-slash-reference) + +Use of triple-slash reference type directives is discouraged in favor of the newer `import` style. This rule allows you to ban use of `/// `, `/// `, or `/// ` directives. + +Consider using this rule in place of [`no-triple-slash-reference`](./no-triple-slash-reference.md) which has been deprecated. + +## Rule Details + +With `{ "path": "never", "types": "never", "lib": "never" }` options set, the following will all be **incorrect** usage: + +```ts +/// +/// +/// +``` + +Examples of **incorrect** code for the `{ "types": "prefer-import" }` option. Note that these are only errors when **both** stlyes are used for the **same** module: + +```ts +/// +import * as foo from 'foo'; +``` + +```ts +/// +import foo = require('foo'); +``` + +With `{ "path": "always", "types": "always", "lib": "always" }` options set, the following will all be **correct** usage: + +```ts +/// +/// +/// +``` + +Examples of **correct** code for the `{ "types": "prefer-import" }` option: + +```ts +import * as foo from 'foo'; +``` + +```ts +import foo = require('foo'); +``` + +## When To Use It + +If you want to ban use of one or all of the triple slash reference directives, or any time you might use triple-slash type reference directives and ES6 import declarations in the same file. + +## When Not To Use It + +If you want to use all flavors of triple slash reference directives. + +## Compatibility + +- TSLint: [no-reference](http://palantir.github.io/tslint/rules/no-reference/) +- TSLint: [no-reference-import](https://palantir.github.io/tslint/rules/no-reference-import/) diff --git a/packages/eslint-plugin/src/configs/all.json b/packages/eslint-plugin/src/configs/all.json index 7dc471fdd5d0..cd5b2bb9cbe9 100644 --- a/packages/eslint-plugin/src/configs/all.json +++ b/packages/eslint-plugin/src/configs/all.json @@ -42,7 +42,6 @@ "@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", @@ -65,6 +64,7 @@ "@typescript-eslint/restrict-plus-operands": "error", "semi": "off", "@typescript-eslint/semi": "error", + "@typescript-eslint/triple-slash-reference": "error", "@typescript-eslint/type-annotation-spacing": "error", "@typescript-eslint/unbound-method": "error", "@typescript-eslint/unified-signatures": "error" diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index f93cb788cf7d..00107aa57a8d 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -54,6 +54,7 @@ import requireArraySortCompare from './require-array-sort-compare'; import restrictPlusOperands from './restrict-plus-operands'; import semi from './semi'; import strictBooleanExpressions from './strict-boolean-expressions'; +import tripleSlashReference from './triple-slash-reference'; import typeAnnotationSpacing from './type-annotation-spacing'; import unboundMethod from './unbound-method'; import unifiedSignatures from './unified-signatures'; @@ -115,6 +116,7 @@ export default { 'restrict-plus-operands': restrictPlusOperands, semi: semi, 'strict-boolean-expressions': strictBooleanExpressions, + 'triple-slash-reference': tripleSlashReference, 'type-annotation-spacing': typeAnnotationSpacing, 'unbound-method': unboundMethod, 'unified-signatures': unifiedSignatures, 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 c7780a99bf38..49f164201664 100644 --- a/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts @@ -10,8 +10,10 @@ export default util.createRule({ recommended: 'error', }, schema: [], + deprecated: true, + replacedBy: ['triple-slash-reference'], messages: { - tripleSlashReference: 'Do not use a triple slash reference.', + noTripleSlashReference: 'Do not use a triple slash reference.', }, }, defaultOptions: [], @@ -30,7 +32,7 @@ export default util.createRule({ if (referenceRegExp.test(comment.value)) { context.report({ node: comment, - messageId: 'tripleSlashReference', + messageId: 'noTripleSlashReference', }); } }); diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts new file mode 100644 index 000000000000..286f445c9661 --- /dev/null +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -0,0 +1,129 @@ +import * as util from '../util'; +import { + Literal, + Node, + TSExternalModuleReference, +} from '@typescript-eslint/typescript-estree/dist/ts-estree/ts-estree'; +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +type Options = [ + { + lib?: 'always' | 'never'; + path?: 'always' | 'never'; + types?: 'always' | 'never' | 'prefer-import'; + } +]; +type MessageIds = 'tripleSlashReference'; + +export default util.createRule({ + name: 'triple-slash-reference', + meta: { + type: 'suggestion', + docs: { + description: + 'Sets preference level for triple slash directives versus ES6-style import declarations', + category: 'Best Practices', + recommended: false, + }, + messages: { + tripleSlashReference: + 'Do not use a triple slash reference for {{module}}, use `import` style instead.', + }, + schema: [ + { + type: 'object', + properties: { + lib: { + enum: ['always', 'never'], + }, + path: { + enum: ['always', 'never'], + }, + types: { + enum: ['always', 'never', 'prefer-import'], + }, + }, + additionalProperties: false, + }, + ], + }, + defaultOptions: [ + { + lib: 'always', + path: 'never', + types: 'prefer-import', + }, + ], + create(context, [{ lib, path, types }]) { + let programNode: Node; + const sourceCode = context.getSourceCode(); + const references: ({ + comment: TSESTree.Comment; + importName: string; + })[] = []; + + function hasMatchingReference(source: Literal) { + references.forEach(reference => { + if (reference.importName === source.value) { + context.report({ + node: reference.comment, + messageId: 'tripleSlashReference', + data: { + module: reference.importName, + }, + }); + } + }); + } + return { + ImportDeclaration(node) { + if (programNode) { + const source = node.source as Literal; + hasMatchingReference(source); + } + }, + TSImportEqualsDeclaration(node) { + if (programNode) { + const source = (node.moduleReference as TSExternalModuleReference) + .expression as Literal; + hasMatchingReference(source); + } + }, + Program(node) { + if (lib === 'always' && path === 'always' && types == 'always') { + return; + } + programNode = node; + const referenceRegExp = /^\/\s* { + if (comment.type !== 'Line') { + return; + } + const referenceResult = referenceRegExp.exec(comment.value); + + if (referenceResult) { + if ( + (referenceResult[1] === 'types' && types === 'never') || + (referenceResult[1] === 'path' && path === 'never') || + (referenceResult[1] === 'lib' && lib === 'never') + ) { + context.report({ + node: comment, + messageId: 'tripleSlashReference', + data: { + module: referenceResult[2], + }, + }); + return; + } + if (referenceResult[1] === 'types' && types === 'prefer-import') { + references.push({ comment, importName: referenceResult[2] }); + } + } + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts b/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts index 5beaf104e52b..f120d5262432 100644 --- a/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts +++ b/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts @@ -22,7 +22,7 @@ let a code: '/// ', errors: [ { - messageId: 'tripleSlashReference', + messageId: 'noTripleSlashReference', line: 1, column: 1, }, @@ -36,7 +36,7 @@ let a parser: '@typescript-eslint/parser', errors: [ { - messageId: 'tripleSlashReference', + messageId: 'noTripleSlashReference', line: 2, column: 1, }, diff --git a/packages/eslint-plugin/tests/rules/triple-slash-reference.test.ts b/packages/eslint-plugin/tests/rules/triple-slash-reference.test.ts new file mode 100644 index 000000000000..0785bf7a02ee --- /dev/null +++ b/packages/eslint-plugin/tests/rules/triple-slash-reference.test.ts @@ -0,0 +1,128 @@ +import rule from '../../src/rules/triple-slash-reference'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('triple-slash-reference', rule, { + valid: [ + { + code: ` + /// + /// + /// + import * as foo from "foo" + import * as bar from "bar" + import * as baz from "baz" + `, + options: [{ path: 'always', types: 'always', lib: 'always' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ path: 'never' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ types: 'never' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ lib: 'never' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ types: 'prefer-import' }], + }, + { + code: ` + /// + import * as bar from "bar" + `, + options: [{ types: 'prefer-import' }], + }, + { + code: ` + /* + /// + */ + import * as foo from "foo" + `, + options: [{ path: 'never', types: 'never', lib: 'never' }], + }, + ], + invalid: [ + { + code: ` +/// +import * as foo from "foo" + `, + options: [{ types: 'prefer-import' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/// +import foo = require("foo"); + `, + options: [{ types: 'prefer-import' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 2, + column: 1, + }, + ], + }, + { + code: `/// `, + options: [{ path: 'never' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 1, + column: 1, + }, + ], + }, + { + code: `/// `, + options: [{ types: 'never' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 1, + column: 1, + }, + ], + }, + { + code: `/// `, + options: [{ lib: 'never' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 1, + column: 1, + }, + ], + }, + ], +}); From 6de19d3357706903c0ba7a73ef35aac6bfe048e7 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 8 Jul 2019 05:43:47 -0700 Subject: [PATCH 10/13] fix(typescript-estree): fix `async` identifier token typed as `Keyword` (#681) --- .../lib/__snapshots__/typescript.ts.snap | 608 +++++++ .../basics/keyword-variables.src.ts | 13 + packages/typescript-estree/src/node-utils.ts | 23 +- .../semantic-diagnostics-enabled.ts.snap | 2 + .../lib/__snapshots__/typescript.ts.snap | 1436 +++++++++++++++++ 5 files changed, 2071 insertions(+), 11 deletions(-) create mode 100644 packages/shared-fixtures/fixtures/typescript/basics/keyword-variables.src.ts diff --git a/packages/parser/tests/lib/__snapshots__/typescript.ts.snap b/packages/parser/tests/lib/__snapshots__/typescript.ts.snap index aab288144990..2e00218b783a 100644 --- a/packages/parser/tests/lib/__snapshots__/typescript.ts.snap +++ b/packages/parser/tests/lib/__snapshots__/typescript.ts.snap @@ -18534,6 +18534,614 @@ Object { } `; +exports[`typescript fixtures/basics/keyword-variables.src 1`] = ` +Object { + "$id": 11, + "block": Object { + "range": Array [ + 0, + 154, + ], + "type": "Program", + }, + "childScopes": Array [ + Object { + "$id": 10, + "block": Object { + "range": Array [ + 0, + 154, + ], + "type": "Program", + }, + "childScopes": Array [], + "functionExpressionScope": false, + "isStrict": true, + "references": Array [ + Object { + "$id": 5, + "from": Object { + "$ref": 10, + }, + "identifier": Object { + "name": "get", + "range": Array [ + 6, + 9, + ], + "type": "Identifier", + }, + "kind": "w", + "resolved": Object { + "$ref": 0, + }, + "writeExpr": Object { + "range": Array [ + 12, + 13, + ], + "type": "Literal", + }, + }, + Object { + "$id": 6, + "from": Object { + "$ref": 10, + }, + "identifier": Object { + "name": "set", + "range": Array [ + 21, + 24, + ], + "type": "Identifier", + }, + "kind": "w", + "resolved": Object { + "$ref": 1, + }, + "writeExpr": Object { + "range": Array [ + 27, + 28, + ], + "type": "Literal", + }, + }, + Object { + "$id": 7, + "from": Object { + "$ref": 10, + }, + "identifier": Object { + "name": "module", + "range": Array [ + 36, + 42, + ], + "type": "Identifier", + }, + "kind": "w", + "resolved": Object { + "$ref": 2, + }, + "writeExpr": Object { + "range": Array [ + 45, + 46, + ], + "type": "Literal", + }, + }, + Object { + "$id": 8, + "from": Object { + "$ref": 10, + }, + "identifier": Object { + "name": "type", + "range": Array [ + 54, + 58, + ], + "type": "Identifier", + }, + "kind": "w", + "resolved": Object { + "$ref": 3, + }, + "writeExpr": Object { + "range": Array [ + 61, + 62, + ], + "type": "Literal", + }, + }, + Object { + "$id": 9, + "from": Object { + "$ref": 10, + }, + "identifier": Object { + "name": "async", + "range": Array [ + 70, + 75, + ], + "type": "Identifier", + }, + "kind": "w", + "resolved": Object { + "$ref": 4, + }, + "writeExpr": Object { + "range": Array [ + 78, + 79, + ], + "type": "Literal", + }, + }, + ], + "throughReferences": Array [], + "type": "module", + "upperScope": Object { + "$ref": 11, + }, + "variableMap": Object { + "async": Object { + "$ref": 4, + }, + "get": Object { + "$ref": 0, + }, + "module": Object { + "$ref": 2, + }, + "set": Object { + "$ref": 1, + }, + "type": Object { + "$ref": 3, + }, + }, + "variableScope": Object { + "$ref": 10, + }, + "variables": Array [ + Object { + "$id": 0, + "defs": Array [ + Object { + "name": Object { + "name": "get", + "range": Array [ + 6, + 9, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 6, + 13, + ], + "type": "VariableDeclarator", + }, + "parent": Object { + "range": Array [ + 0, + 14, + ], + "type": "VariableDeclaration", + }, + "type": "Variable", + }, + Object { + "name": Object { + "name": "get", + "range": Array [ + 93, + 96, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 93, + 96, + ], + "type": "ImportSpecifier", + }, + "parent": Object { + "range": Array [ + 82, + 153, + ], + "type": "ImportDeclaration", + }, + "type": "ImportBinding", + }, + ], + "eslintUsed": undefined, + "identifiers": Array [ + Object { + "name": "get", + "range": Array [ + 6, + 9, + ], + "type": "Identifier", + }, + Object { + "name": "get", + "range": Array [ + 93, + 96, + ], + "type": "Identifier", + }, + ], + "name": "get", + "references": Array [ + Object { + "$ref": 5, + }, + ], + "scope": Object { + "$ref": 10, + }, + }, + Object { + "$id": 1, + "defs": Array [ + Object { + "name": Object { + "name": "set", + "range": Array [ + 21, + 24, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 21, + 28, + ], + "type": "VariableDeclarator", + }, + "parent": Object { + "range": Array [ + 15, + 29, + ], + "type": "VariableDeclaration", + }, + "type": "Variable", + }, + Object { + "name": Object { + "name": "set", + "range": Array [ + 100, + 103, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 100, + 103, + ], + "type": "ImportSpecifier", + }, + "parent": Object { + "range": Array [ + 82, + 153, + ], + "type": "ImportDeclaration", + }, + "type": "ImportBinding", + }, + ], + "eslintUsed": undefined, + "identifiers": Array [ + Object { + "name": "set", + "range": Array [ + 21, + 24, + ], + "type": "Identifier", + }, + Object { + "name": "set", + "range": Array [ + 100, + 103, + ], + "type": "Identifier", + }, + ], + "name": "set", + "references": Array [ + Object { + "$ref": 6, + }, + ], + "scope": Object { + "$ref": 10, + }, + }, + Object { + "$id": 2, + "defs": Array [ + Object { + "name": Object { + "name": "module", + "range": Array [ + 36, + 42, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 36, + 46, + ], + "type": "VariableDeclarator", + }, + "parent": Object { + "range": Array [ + 30, + 47, + ], + "type": "VariableDeclaration", + }, + "type": "Variable", + }, + Object { + "name": Object { + "name": "module", + "range": Array [ + 107, + 113, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 107, + 113, + ], + "type": "ImportSpecifier", + }, + "parent": Object { + "range": Array [ + 82, + 153, + ], + "type": "ImportDeclaration", + }, + "type": "ImportBinding", + }, + ], + "eslintUsed": undefined, + "identifiers": Array [ + Object { + "name": "module", + "range": Array [ + 36, + 42, + ], + "type": "Identifier", + }, + Object { + "name": "module", + "range": Array [ + 107, + 113, + ], + "type": "Identifier", + }, + ], + "name": "module", + "references": Array [ + Object { + "$ref": 7, + }, + ], + "scope": Object { + "$ref": 10, + }, + }, + Object { + "$id": 3, + "defs": Array [ + Object { + "name": Object { + "name": "type", + "range": Array [ + 54, + 58, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 54, + 62, + ], + "type": "VariableDeclarator", + }, + "parent": Object { + "range": Array [ + 48, + 63, + ], + "type": "VariableDeclaration", + }, + "type": "Variable", + }, + Object { + "name": Object { + "name": "type", + "range": Array [ + 117, + 121, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 117, + 121, + ], + "type": "ImportSpecifier", + }, + "parent": Object { + "range": Array [ + 82, + 153, + ], + "type": "ImportDeclaration", + }, + "type": "ImportBinding", + }, + ], + "eslintUsed": undefined, + "identifiers": Array [ + Object { + "name": "type", + "range": Array [ + 54, + 58, + ], + "type": "Identifier", + }, + Object { + "name": "type", + "range": Array [ + 117, + 121, + ], + "type": "Identifier", + }, + ], + "name": "type", + "references": Array [ + Object { + "$ref": 8, + }, + ], + "scope": Object { + "$ref": 10, + }, + }, + Object { + "$id": 4, + "defs": Array [ + Object { + "name": Object { + "name": "async", + "range": Array [ + 70, + 75, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 70, + 79, + ], + "type": "VariableDeclarator", + }, + "parent": Object { + "range": Array [ + 64, + 80, + ], + "type": "VariableDeclaration", + }, + "type": "Variable", + }, + Object { + "name": Object { + "name": "async", + "range": Array [ + 125, + 130, + ], + "type": "Identifier", + }, + "node": Object { + "range": Array [ + 125, + 130, + ], + "type": "ImportSpecifier", + }, + "parent": Object { + "range": Array [ + 82, + 153, + ], + "type": "ImportDeclaration", + }, + "type": "ImportBinding", + }, + ], + "eslintUsed": undefined, + "identifiers": Array [ + Object { + "name": "async", + "range": Array [ + 70, + 75, + ], + "type": "Identifier", + }, + Object { + "name": "async", + "range": Array [ + 125, + 130, + ], + "type": "Identifier", + }, + ], + "name": "async", + "references": Array [ + Object { + "$ref": 9, + }, + ], + "scope": Object { + "$ref": 10, + }, + }, + ], + }, + ], + "functionExpressionScope": false, + "isStrict": false, + "references": Array [], + "throughReferences": Array [], + "type": "global", + "upperScope": null, + "variableMap": Object {}, + "variableScope": Object { + "$ref": 11, + }, + "variables": Array [], +} +`; + exports[`typescript fixtures/basics/nested-type-arguments.src 1`] = ` Object { "$id": 2, diff --git a/packages/shared-fixtures/fixtures/typescript/basics/keyword-variables.src.ts b/packages/shared-fixtures/fixtures/typescript/basics/keyword-variables.src.ts new file mode 100644 index 000000000000..f4cbb3212bf1 --- /dev/null +++ b/packages/shared-fixtures/fixtures/typescript/basics/keyword-variables.src.ts @@ -0,0 +1,13 @@ +const get = 1; +const set = 1; +const module = 1; +const type = 1; +const async = 1; + +import { + get, + set, + module, + type, + async, +} from 'fake-module'; diff --git a/packages/typescript-estree/src/node-utils.ts b/packages/typescript-estree/src/node-utils.ts index d682bb091b4e..a8d1b137d1c2 100644 --- a/packages/typescript-estree/src/node-utils.ts +++ b/packages/typescript-estree/src/node-utils.ts @@ -455,6 +455,7 @@ export function getTokenType(token: any): AST_TOKEN_TYPES { case SyntaxKind.SetKeyword: case SyntaxKind.TypeKeyword: case SyntaxKind.ModuleKeyword: + case SyntaxKind.AsyncKeyword: return AST_TOKEN_TYPES.Identifier; default: @@ -550,17 +551,17 @@ export function convertToken( ast: ts.SourceFile, ): TSESTree.Token { const start = - token.kind === SyntaxKind.JsxText - ? token.getFullStart() - : token.getStart(ast), - end = token.getEnd(), - value = ast.text.slice(start, end), - newToken: TSESTree.Token = { - type: getTokenType(token), - value, - range: [start, end], - loc: getLocFor(start, end, ast), - }; + token.kind === SyntaxKind.JsxText + ? token.getFullStart() + : token.getStart(ast); + const end = token.getEnd(); + const value = ast.text.slice(start, end); + const newToken: TSESTree.Token = { + type: getTokenType(token), + value, + range: [start, end], + loc: getLocFor(start, end, ast), + }; if (newToken.type === 'RegularExpression') { newToken.regex = { 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 227932cd9360..7af41c0f3025 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 @@ -1883,6 +1883,8 @@ exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" e exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/keyof-operator.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; +exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/keyword-variables.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; + exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/nested-type-arguments.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; exports[`Parse all fixtures with "errorOnTypeScriptSyntacticAndSemanticIssues" enabled fixtures/typescript/basics/never-type-param.src 1`] = `"TEST OUTPUT: No semantic or syntactic issues found"`; diff --git a/packages/typescript-estree/tests/lib/__snapshots__/typescript.ts.snap b/packages/typescript-estree/tests/lib/__snapshots__/typescript.ts.snap index c8f0cf9e3649..0859b0362508 100644 --- a/packages/typescript-estree/tests/lib/__snapshots__/typescript.ts.snap +++ b/packages/typescript-estree/tests/lib/__snapshots__/typescript.ts.snap @@ -58608,6 +58608,1442 @@ Object { } `; +exports[`typescript fixtures/basics/keyword-variables.src 1`] = ` +Object { + "body": Array [ + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "name": "get", + "range": Array [ + 6, + 9, + ], + "type": "Identifier", + }, + "init": Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 12, + "line": 1, + }, + }, + "range": Array [ + 12, + 13, + ], + "raw": "1", + "type": "Literal", + "value": 1, + }, + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 13, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 14, + "line": 1, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 14, + ], + "type": "VariableDeclaration", + }, + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 2, + }, + "start": Object { + "column": 6, + "line": 2, + }, + }, + "name": "set", + "range": Array [ + 21, + 24, + ], + "type": "Identifier", + }, + "init": Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 2, + }, + "start": Object { + "column": 12, + "line": 2, + }, + }, + "range": Array [ + 27, + 28, + ], + "raw": "1", + "type": "Literal", + "value": 1, + }, + "loc": Object { + "end": Object { + "column": 13, + "line": 2, + }, + "start": Object { + "column": 6, + "line": 2, + }, + }, + "range": Array [ + 21, + 28, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 14, + "line": 2, + }, + "start": Object { + "column": 0, + "line": 2, + }, + }, + "range": Array [ + 15, + 29, + ], + "type": "VariableDeclaration", + }, + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 3, + }, + "start": Object { + "column": 6, + "line": 3, + }, + }, + "name": "module", + "range": Array [ + 36, + 42, + ], + "type": "Identifier", + }, + "init": Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 45, + 46, + ], + "raw": "1", + "type": "Literal", + "value": 1, + }, + "loc": Object { + "end": Object { + "column": 16, + "line": 3, + }, + "start": Object { + "column": 6, + "line": 3, + }, + }, + "range": Array [ + 36, + 46, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 17, + "line": 3, + }, + "start": Object { + "column": 0, + "line": 3, + }, + }, + "range": Array [ + 30, + 47, + ], + "type": "VariableDeclaration", + }, + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 4, + }, + "start": Object { + "column": 6, + "line": 4, + }, + }, + "name": "type", + "range": Array [ + 54, + 58, + ], + "type": "Identifier", + }, + "init": Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 13, + "line": 4, + }, + }, + "range": Array [ + 61, + 62, + ], + "raw": "1", + "type": "Literal", + "value": 1, + }, + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 6, + "line": 4, + }, + }, + "range": Array [ + 54, + 62, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 15, + "line": 4, + }, + "start": Object { + "column": 0, + "line": 4, + }, + }, + "range": Array [ + 48, + 63, + ], + "type": "VariableDeclaration", + }, + Object { + "declarations": Array [ + Object { + "id": Object { + "loc": Object { + "end": Object { + "column": 11, + "line": 5, + }, + "start": Object { + "column": 6, + "line": 5, + }, + }, + "name": "async", + "range": Array [ + 70, + 75, + ], + "type": "Identifier", + }, + "init": Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "range": Array [ + 78, + 79, + ], + "raw": "1", + "type": "Literal", + "value": 1, + }, + "loc": Object { + "end": Object { + "column": 15, + "line": 5, + }, + "start": Object { + "column": 6, + "line": 5, + }, + }, + "range": Array [ + 70, + 79, + ], + "type": "VariableDeclarator", + }, + ], + "kind": "const", + "loc": Object { + "end": Object { + "column": 16, + "line": 5, + }, + "start": Object { + "column": 0, + "line": 5, + }, + }, + "range": Array [ + 64, + 80, + ], + "type": "VariableDeclaration", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 13, + }, + "start": Object { + "column": 0, + "line": 7, + }, + }, + "range": Array [ + 82, + 153, + ], + "source": Object { + "loc": Object { + "end": Object { + "column": 20, + "line": 13, + }, + "start": Object { + "column": 7, + "line": 13, + }, + }, + "range": Array [ + 139, + 152, + ], + "raw": "'fake-module'", + "type": "Literal", + "value": "fake-module", + }, + "specifiers": Array [ + Object { + "imported": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 2, + "line": 8, + }, + }, + "name": "get", + "range": Array [ + 93, + 96, + ], + "type": "Identifier", + }, + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 2, + "line": 8, + }, + }, + "local": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 2, + "line": 8, + }, + }, + "name": "get", + "range": Array [ + 93, + 96, + ], + "type": "Identifier", + }, + "range": Array [ + 93, + 96, + ], + "type": "ImportSpecifier", + }, + Object { + "imported": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 9, + }, + "start": Object { + "column": 2, + "line": 9, + }, + }, + "name": "set", + "range": Array [ + 100, + 103, + ], + "type": "Identifier", + }, + "loc": Object { + "end": Object { + "column": 5, + "line": 9, + }, + "start": Object { + "column": 2, + "line": 9, + }, + }, + "local": Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 9, + }, + "start": Object { + "column": 2, + "line": 9, + }, + }, + "name": "set", + "range": Array [ + 100, + 103, + ], + "type": "Identifier", + }, + "range": Array [ + 100, + 103, + ], + "type": "ImportSpecifier", + }, + Object { + "imported": Object { + "loc": Object { + "end": Object { + "column": 8, + "line": 10, + }, + "start": Object { + "column": 2, + "line": 10, + }, + }, + "name": "module", + "range": Array [ + 107, + 113, + ], + "type": "Identifier", + }, + "loc": Object { + "end": Object { + "column": 8, + "line": 10, + }, + "start": Object { + "column": 2, + "line": 10, + }, + }, + "local": Object { + "loc": Object { + "end": Object { + "column": 8, + "line": 10, + }, + "start": Object { + "column": 2, + "line": 10, + }, + }, + "name": "module", + "range": Array [ + 107, + 113, + ], + "type": "Identifier", + }, + "range": Array [ + 107, + 113, + ], + "type": "ImportSpecifier", + }, + Object { + "imported": Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 11, + }, + "start": Object { + "column": 2, + "line": 11, + }, + }, + "name": "type", + "range": Array [ + 117, + 121, + ], + "type": "Identifier", + }, + "loc": Object { + "end": Object { + "column": 6, + "line": 11, + }, + "start": Object { + "column": 2, + "line": 11, + }, + }, + "local": Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 11, + }, + "start": Object { + "column": 2, + "line": 11, + }, + }, + "name": "type", + "range": Array [ + 117, + 121, + ], + "type": "Identifier", + }, + "range": Array [ + 117, + 121, + ], + "type": "ImportSpecifier", + }, + Object { + "imported": Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 12, + }, + "start": Object { + "column": 2, + "line": 12, + }, + }, + "name": "async", + "range": Array [ + 125, + 130, + ], + "type": "Identifier", + }, + "loc": Object { + "end": Object { + "column": 7, + "line": 12, + }, + "start": Object { + "column": 2, + "line": 12, + }, + }, + "local": Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 12, + }, + "start": Object { + "column": 2, + "line": 12, + }, + }, + "name": "async", + "range": Array [ + 125, + 130, + ], + "type": "Identifier", + }, + "range": Array [ + 125, + 130, + ], + "type": "ImportSpecifier", + }, + ], + "type": "ImportDeclaration", + }, + ], + "loc": Object { + "end": Object { + "column": 0, + "line": 14, + }, + "start": Object { + "column": 0, + "line": 1, + }, + }, + "range": Array [ + 0, + 154, + ], + "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": 9, + "line": 1, + }, + "start": Object { + "column": 6, + "line": 1, + }, + }, + "range": Array [ + 6, + 9, + ], + "type": "Identifier", + "value": "get", + }, + Object { + "loc": Object { + "end": Object { + "column": 11, + "line": 1, + }, + "start": Object { + "column": 10, + "line": 1, + }, + }, + "range": Array [ + 10, + 11, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 1, + }, + "start": Object { + "column": 12, + "line": 1, + }, + }, + "range": Array [ + 12, + 13, + ], + "type": "Numeric", + "value": "1", + }, + 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": 5, + "line": 2, + }, + "start": Object { + "column": 0, + "line": 2, + }, + }, + "range": Array [ + 15, + 20, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 2, + }, + "start": Object { + "column": 6, + "line": 2, + }, + }, + "range": Array [ + 21, + 24, + ], + "type": "Identifier", + "value": "set", + }, + Object { + "loc": Object { + "end": Object { + "column": 11, + "line": 2, + }, + "start": Object { + "column": 10, + "line": 2, + }, + }, + "range": Array [ + 25, + 26, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 2, + }, + "start": Object { + "column": 12, + "line": 2, + }, + }, + "range": Array [ + 27, + 28, + ], + "type": "Numeric", + "value": "1", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 2, + }, + "start": Object { + "column": 13, + "line": 2, + }, + }, + "range": Array [ + 28, + 29, + ], + "type": "Punctuator", + "value": ";", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 3, + }, + "start": Object { + "column": 0, + "line": 3, + }, + }, + "range": Array [ + 30, + 35, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 3, + }, + "start": Object { + "column": 6, + "line": 3, + }, + }, + "range": Array [ + 36, + 42, + ], + "type": "Identifier", + "value": "module", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 3, + }, + "start": Object { + "column": 13, + "line": 3, + }, + }, + "range": Array [ + 43, + 44, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 3, + }, + "start": Object { + "column": 15, + "line": 3, + }, + }, + "range": Array [ + 45, + 46, + ], + "type": "Numeric", + "value": "1", + }, + Object { + "loc": Object { + "end": Object { + "column": 17, + "line": 3, + }, + "start": Object { + "column": 16, + "line": 3, + }, + }, + "range": Array [ + 46, + 47, + ], + "type": "Punctuator", + "value": ";", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 4, + }, + "start": Object { + "column": 0, + "line": 4, + }, + }, + "range": Array [ + 48, + 53, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 10, + "line": 4, + }, + "start": Object { + "column": 6, + "line": 4, + }, + }, + "range": Array [ + 54, + 58, + ], + "type": "Identifier", + "value": "type", + }, + Object { + "loc": Object { + "end": Object { + "column": 12, + "line": 4, + }, + "start": Object { + "column": 11, + "line": 4, + }, + }, + "range": Array [ + 59, + 60, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 14, + "line": 4, + }, + "start": Object { + "column": 13, + "line": 4, + }, + }, + "range": Array [ + 61, + 62, + ], + "type": "Numeric", + "value": "1", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 4, + }, + "start": Object { + "column": 14, + "line": 4, + }, + }, + "range": Array [ + 62, + 63, + ], + "type": "Punctuator", + "value": ";", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 5, + }, + "start": Object { + "column": 0, + "line": 5, + }, + }, + "range": Array [ + 64, + 69, + ], + "type": "Keyword", + "value": "const", + }, + Object { + "loc": Object { + "end": Object { + "column": 11, + "line": 5, + }, + "start": Object { + "column": 6, + "line": 5, + }, + }, + "range": Array [ + 70, + 75, + ], + "type": "Identifier", + "value": "async", + }, + Object { + "loc": Object { + "end": Object { + "column": 13, + "line": 5, + }, + "start": Object { + "column": 12, + "line": 5, + }, + }, + "range": Array [ + 76, + 77, + ], + "type": "Punctuator", + "value": "=", + }, + Object { + "loc": Object { + "end": Object { + "column": 15, + "line": 5, + }, + "start": Object { + "column": 14, + "line": 5, + }, + }, + "range": Array [ + 78, + 79, + ], + "type": "Numeric", + "value": "1", + }, + Object { + "loc": Object { + "end": Object { + "column": 16, + "line": 5, + }, + "start": Object { + "column": 15, + "line": 5, + }, + }, + "range": Array [ + 79, + 80, + ], + "type": "Punctuator", + "value": ";", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 7, + }, + "start": Object { + "column": 0, + "line": 7, + }, + }, + "range": Array [ + 82, + 88, + ], + "type": "Keyword", + "value": "import", + }, + Object { + "loc": Object { + "end": Object { + "column": 8, + "line": 7, + }, + "start": Object { + "column": 7, + "line": 7, + }, + }, + "range": Array [ + 89, + 90, + ], + "type": "Punctuator", + "value": "{", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 8, + }, + "start": Object { + "column": 2, + "line": 8, + }, + }, + "range": Array [ + 93, + 96, + ], + "type": "Identifier", + "value": "get", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 8, + }, + "start": Object { + "column": 5, + "line": 8, + }, + }, + "range": Array [ + 96, + 97, + ], + "type": "Punctuator", + "value": ",", + }, + Object { + "loc": Object { + "end": Object { + "column": 5, + "line": 9, + }, + "start": Object { + "column": 2, + "line": 9, + }, + }, + "range": Array [ + 100, + 103, + ], + "type": "Identifier", + "value": "set", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 9, + }, + "start": Object { + "column": 5, + "line": 9, + }, + }, + "range": Array [ + 103, + 104, + ], + "type": "Punctuator", + "value": ",", + }, + Object { + "loc": Object { + "end": Object { + "column": 8, + "line": 10, + }, + "start": Object { + "column": 2, + "line": 10, + }, + }, + "range": Array [ + 107, + 113, + ], + "type": "Identifier", + "value": "module", + }, + Object { + "loc": Object { + "end": Object { + "column": 9, + "line": 10, + }, + "start": Object { + "column": 8, + "line": 10, + }, + }, + "range": Array [ + 113, + 114, + ], + "type": "Punctuator", + "value": ",", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 11, + }, + "start": Object { + "column": 2, + "line": 11, + }, + }, + "range": Array [ + 117, + 121, + ], + "type": "Identifier", + "value": "type", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 11, + }, + "start": Object { + "column": 6, + "line": 11, + }, + }, + "range": Array [ + 121, + 122, + ], + "type": "Punctuator", + "value": ",", + }, + Object { + "loc": Object { + "end": Object { + "column": 7, + "line": 12, + }, + "start": Object { + "column": 2, + "line": 12, + }, + }, + "range": Array [ + 125, + 130, + ], + "type": "Identifier", + "value": "async", + }, + Object { + "loc": Object { + "end": Object { + "column": 8, + "line": 12, + }, + "start": Object { + "column": 7, + "line": 12, + }, + }, + "range": Array [ + 130, + 131, + ], + "type": "Punctuator", + "value": ",", + }, + Object { + "loc": Object { + "end": Object { + "column": 1, + "line": 13, + }, + "start": Object { + "column": 0, + "line": 13, + }, + }, + "range": Array [ + 132, + 133, + ], + "type": "Punctuator", + "value": "}", + }, + Object { + "loc": Object { + "end": Object { + "column": 6, + "line": 13, + }, + "start": Object { + "column": 2, + "line": 13, + }, + }, + "range": Array [ + 134, + 138, + ], + "type": "Identifier", + "value": "from", + }, + Object { + "loc": Object { + "end": Object { + "column": 20, + "line": 13, + }, + "start": Object { + "column": 7, + "line": 13, + }, + }, + "range": Array [ + 139, + 152, + ], + "type": "String", + "value": "'fake-module'", + }, + Object { + "loc": Object { + "end": Object { + "column": 21, + "line": 13, + }, + "start": Object { + "column": 20, + "line": 13, + }, + }, + "range": Array [ + 152, + 153, + ], + "type": "Punctuator", + "value": ";", + }, + ], + "type": "Program", +} +`; + exports[`typescript fixtures/basics/nested-type-arguments.src 1`] = ` Object { "body": Array [ From e0aeb184049084748e613b8d181d97ce19aaaa37 Mon Sep 17 00:00:00 2001 From: Brad Zacher Date: Mon, 8 Jul 2019 07:31:27 -0700 Subject: [PATCH 11/13] feat(eslint-plugin): [explicit-function-return-type] add handling for usage as arguments (#680) --- .../rules/explicit-function-return-type.md | 3 + .../rules/explicit-function-return-type.ts | 18 ++- .../explicit-function-return-type.test.ts | 119 ++++++++++++++++++ 3 files changed, 139 insertions(+), 1 deletion(-) 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 a20aa83c7a5d..9aacb0acafc7 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -138,6 +138,9 @@ let objectPropAs = { let objectPropCast = { foo: () => 1, }; + +declare functionWithArg(arg: () => number); +functionWithArg(() => 1); ``` ### allowHigherOrderFunctions 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 876f42517d61..d5176a43af5c 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -187,6 +187,21 @@ export default util.createRule({ ); } + /** + * Checks if a node belongs to: + * `foo(() => 1)` + */ + function isFunctionArgument( + parent: TSESTree.Node, + child: TSESTree.Node, + ): boolean { + return ( + parent.type === AST_NODE_TYPES.CallExpression && + // make sure this isn't an IIFE + parent.callee !== child + ); + } + /** * Checks if a function declaration/expression has a return type. */ @@ -232,7 +247,8 @@ export default util.createRule({ isTypeCast(node.parent) || isVariableDeclaratorWithTypeAnnotation(node.parent) || isClassPropertyWithTypeAnnotation(node.parent) || - isPropertyOfObjectWithType(node.parent) + isPropertyOfObjectWithType(node.parent) || + isFunctionArgument(node.parent, node) ) { return; } 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 1b9209b7abf0..eb67f620be1e 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 @@ -245,6 +245,42 @@ function FunctionDeclaration() { `, options: [{ allowHigherOrderFunctions: true }], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/679 + { + filename: 'test.ts', + code: ` +declare function foo(arg: () => void): void +foo(() => 1) +foo(() => {}) +foo(() => null) +foo(() => true) +foo(() => '') + `, + options: [ + { + allowTypedFunctionExpressions: true, + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Accumulator { + private count: number = 0; + + public accumulate(fn: () => number): void { + this.count += fn(); + } +} + +new Accumulator().accumulate(() => 1); + `, + options: [ + { + allowTypedFunctionExpressions: true, + }, + ], + }, ], invalid: [ { @@ -550,5 +586,88 @@ function FunctionDeclaration() { }, ], }, + // https://github.com/typescript-eslint/typescript-eslint/issues/679 + { + filename: 'test.ts', + code: ` +declare function foo(arg: () => void): void +foo(() => 1) +foo(() => {}) +foo(() => null) +foo(() => true) +foo(() => '') + `, + options: [ + { + allowTypedFunctionExpressions: false, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 3, + }, + { + messageId: 'missingReturnType', + line: 4, + }, + { + messageId: 'missingReturnType', + line: 5, + }, + { + messageId: 'missingReturnType', + line: 6, + }, + { + messageId: 'missingReturnType', + line: 7, + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Accumulator { + private count: number = 0; + + public accumulate(fn: () => number): void { + this.count += fn(); + } +} + +new Accumulator().accumulate(() => 1); + `, + options: [ + { + allowTypedFunctionExpressions: false, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 10, + column: 30, + }, + ], + }, + { + filename: 'test.ts', + code: ` +(() => true)() + `, + options: [ + { + allowTypedFunctionExpressions: false, + }, + ], + errors: [ + { + messageId: 'missingReturnType', + line: 2, + column: 2, + }, + ], + }, ], }); From b30e78d894806638417cd99c6d3fe4a74cbde7ec Mon Sep 17 00:00:00 2001 From: "dependabot[bot]" <49699333+dependabot[bot]@users.noreply.github.com> Date: Thu, 11 Jul 2019 08:03:56 -0700 Subject: [PATCH 12/13] chore(deps): bump lodash.template from 4.4.0 to 4.5.0 (#693) Bumps [lodash.template](https://github.com/lodash/lodash) from 4.4.0 to 4.5.0. - [Release notes](https://github.com/lodash/lodash/releases) - [Commits](https://github.com/lodash/lodash/compare/4.4.0...4.5.0) Signed-off-by: dependabot[bot] --- yarn.lock | 18 +++++++++--------- 1 file changed, 9 insertions(+), 9 deletions(-) diff --git a/yarn.lock b/yarn.lock index 53025c9063ac..c6bebb98da3a 100644 --- a/yarn.lock +++ b/yarn.lock @@ -4712,7 +4712,7 @@ locate-path@^3.0.0: p-locate "^3.0.0" path-exists "^3.0.0" -lodash._reinterpolate@~3.0.0: +lodash._reinterpolate@^3.0.0: version "3.0.0" resolved "https://registry.yarnpkg.com/lodash._reinterpolate/-/lodash._reinterpolate-3.0.0.tgz#0ccf2d89166af03b3663c796538b75ac6e114d9d" integrity sha1-DM8tiRZq8Ds2Y8eWU4t1rG4RTZ0= @@ -4758,19 +4758,19 @@ lodash.sortby@^4.7.0: integrity sha1-7dFMgk4sycHgsKG0K7UhBRakJDg= lodash.template@^4.0.2: - version "4.4.0" - resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.4.0.tgz#e73a0385c8355591746e020b99679c690e68fba0" - integrity sha1-5zoDhcg1VZF0bgILmWecaQ5o+6A= + version "4.5.0" + resolved "https://registry.yarnpkg.com/lodash.template/-/lodash.template-4.5.0.tgz#f976195cf3f347d0d5f52483569fe8031ccce8ab" + integrity sha512-84vYFxIkmidUiFxidA/KjjH9pAycqW+h980j7Fuz5qxRtO9pgB7MDFTdys1N7A5mcucRiDyEq4fusljItR1T/A== dependencies: - lodash._reinterpolate "~3.0.0" + lodash._reinterpolate "^3.0.0" lodash.templatesettings "^4.0.0" lodash.templatesettings@^4.0.0: - version "4.1.0" - resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.1.0.tgz#2b4d4e95ba440d915ff08bc899e4553666713316" - integrity sha1-K01OlbpEDZFf8IvImeRVNmZxMxY= + version "4.2.0" + resolved "https://registry.yarnpkg.com/lodash.templatesettings/-/lodash.templatesettings-4.2.0.tgz#e481310f049d3cf6d47e912ad09313b154f0fb33" + integrity sha512-stgLz+i3Aa9mZgnjr/O+v9ruKZsPsndy7qPZOchbqk2cnTU1ZaldKK+v7m54WoKIyxiuMZTKT2H81F8BeAc3ZQ== dependencies: - lodash._reinterpolate "~3.0.0" + lodash._reinterpolate "^3.0.0" lodash.unescape@4.0.1: version "4.0.1" From cd96c3875e90580c67023d2483c44da76a572eec Mon Sep 17 00:00:00 2001 From: James Henry Date: Fri, 12 Jul 2019 12:52:29 +0000 Subject: [PATCH 13/13] chore: publish v1.12.0 --- CHANGELOG.md | 22 ++++++++++++++++++++++ lerna.json | 2 +- packages/eslint-plugin-tslint/CHANGELOG.md | 11 +++++++++++ packages/eslint-plugin-tslint/package.json | 6 +++--- packages/eslint-plugin/CHANGELOG.md | 21 +++++++++++++++++++++ packages/eslint-plugin/package.json | 4 ++-- packages/experimental-utils/CHANGELOG.md | 8 ++++++++ packages/experimental-utils/package.json | 4 ++-- packages/parser/CHANGELOG.md | 11 +++++++++++ packages/parser/package.json | 8 ++++---- packages/shared-fixtures/CHANGELOG.md | 11 +++++++++++ packages/shared-fixtures/package.json | 2 +- packages/typescript-estree/CHANGELOG.md | 17 +++++++++++++++++ packages/typescript-estree/package.json | 4 ++-- 14 files changed, 116 insertions(+), 15 deletions(-) diff --git a/CHANGELOG.md b/CHANGELOG.md index 36abcf4782d6..f2df49f1cc62 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,28 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) + + +### Bug Fixes + +* **eslint-plugin:** handle `const;` ([#633](https://github.com/typescript-eslint/typescript-eslint/issues/633)) ([430d628](https://github.com/typescript-eslint/typescript-eslint/commit/430d628)), closes [#441](https://github.com/typescript-eslint/typescript-eslint/issues/441) +* **typescript-estree:** fix `async` identifier token typed as `Keyword` ([#681](https://github.com/typescript-eslint/typescript-eslint/issues/681)) ([6de19d3](https://github.com/typescript-eslint/typescript-eslint/commit/6de19d3)) + + +### Features + +* **eslint-plugin:** [ban-types] Support namespaced type ([#616](https://github.com/typescript-eslint/typescript-eslint/issues/616)) ([e325b72](https://github.com/typescript-eslint/typescript-eslint/commit/e325b72)) +* **eslint-plugin:** [explicit-function-return-type] add handling for usage as arguments ([#680](https://github.com/typescript-eslint/typescript-eslint/issues/680)) ([e0aeb18](https://github.com/typescript-eslint/typescript-eslint/commit/e0aeb18)) +* **eslint-plugin:** [no-explicit-any] Add an optional fixer ([#609](https://github.com/typescript-eslint/typescript-eslint/issues/609)) ([606fc70](https://github.com/typescript-eslint/typescript-eslint/commit/606fc70)) +* **eslint-plugin:** Add rule no-reference-import ([#625](https://github.com/typescript-eslint/typescript-eslint/issues/625)) ([af70a59](https://github.com/typescript-eslint/typescript-eslint/commit/af70a59)) +* **eslint-plugin:** add rule strict-boolean-expressions ([#579](https://github.com/typescript-eslint/typescript-eslint/issues/579)) ([34e7d1e](https://github.com/typescript-eslint/typescript-eslint/commit/34e7d1e)) +* **eslint-plugin:** added new rule prefer-readonly ([#555](https://github.com/typescript-eslint/typescript-eslint/issues/555)) ([76b89a5](https://github.com/typescript-eslint/typescript-eslint/commit/76b89a5)) + + + + + # [1.11.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.10.2...v1.11.0) (2019-06-23) diff --git a/lerna.json b/lerna.json index b1a0c8942fb6..d494e08b3c92 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.11.0", + "version": "1.12.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index 26731119b1df..c0e725bdfcbc 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/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.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) + + +### Features + +* **eslint-plugin:** added new rule prefer-readonly ([#555](https://github.com/typescript-eslint/typescript-eslint/issues/555)) ([76b89a5](https://github.com/typescript-eslint/typescript-eslint/commit/76b89a5)) + + + + + # [1.11.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.10.2...v1.11.0) (2019-06-23) **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 d62e49960990..f370f0c58470 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.11.0", + "version": "1.12.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -31,7 +31,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "1.11.0", + "@typescript-eslint/experimental-utils": "1.12.0", "lodash.memoize": "^4.1.2" }, "peerDependencies": { @@ -41,6 +41,6 @@ "devDependencies": { "@types/json-schema": "^7.0.3", "@types/lodash.memoize": "^4.1.4", - "@typescript-eslint/parser": "1.11.0" + "@typescript-eslint/parser": "1.12.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index 497ba5cb7832..e5a04d77763b 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,27 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) + + +### Bug Fixes + +* **eslint-plugin:** handle `const;` ([#633](https://github.com/typescript-eslint/typescript-eslint/issues/633)) ([430d628](https://github.com/typescript-eslint/typescript-eslint/commit/430d628)), closes [#441](https://github.com/typescript-eslint/typescript-eslint/issues/441) + + +### Features + +* **eslint-plugin:** [ban-types] Support namespaced type ([#616](https://github.com/typescript-eslint/typescript-eslint/issues/616)) ([e325b72](https://github.com/typescript-eslint/typescript-eslint/commit/e325b72)) +* **eslint-plugin:** [explicit-function-return-type] add handling for usage as arguments ([#680](https://github.com/typescript-eslint/typescript-eslint/issues/680)) ([e0aeb18](https://github.com/typescript-eslint/typescript-eslint/commit/e0aeb18)) +* **eslint-plugin:** [no-explicit-any] Add an optional fixer ([#609](https://github.com/typescript-eslint/typescript-eslint/issues/609)) ([606fc70](https://github.com/typescript-eslint/typescript-eslint/commit/606fc70)) +* **eslint-plugin:** Add rule no-reference-import ([#625](https://github.com/typescript-eslint/typescript-eslint/issues/625)) ([af70a59](https://github.com/typescript-eslint/typescript-eslint/commit/af70a59)) +* **eslint-plugin:** add rule strict-boolean-expressions ([#579](https://github.com/typescript-eslint/typescript-eslint/issues/579)) ([34e7d1e](https://github.com/typescript-eslint/typescript-eslint/commit/34e7d1e)) +* **eslint-plugin:** added new rule prefer-readonly ([#555](https://github.com/typescript-eslint/typescript-eslint/issues/555)) ([76b89a5](https://github.com/typescript-eslint/typescript-eslint/commit/76b89a5)) + + + + + # [1.11.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.10.2...v1.11.0) (2019-06-23) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index d530f86a12bc..28dcc098a7fd 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "1.11.0", + "version": "1.12.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -41,7 +41,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/experimental-utils": "1.11.0", + "@typescript-eslint/experimental-utils": "1.12.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 0726874992f2..40646cc4204d 100644 --- a/packages/experimental-utils/CHANGELOG.md +++ b/packages/experimental-utils/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.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) + +**Note:** Version bump only for package @typescript-eslint/experimental-utils + + + + + # [1.11.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.10.2...v1.11.0) (2019-06-23) diff --git a/packages/experimental-utils/package.json b/packages/experimental-utils/package.json index fa86431a8e02..d0a85ed8a905 100644 --- a/packages/experimental-utils/package.json +++ b/packages/experimental-utils/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/experimental-utils", - "version": "1.11.0", + "version": "1.12.0", "description": "(Experimental) Utilities for working with TypeScript + ESLint together", "keywords": [ "eslint", @@ -36,7 +36,7 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/typescript-estree": "1.11.0", + "@typescript-eslint/typescript-estree": "1.12.0", "eslint-scope": "^4.0.0" }, "peerDependencies": { diff --git a/packages/parser/CHANGELOG.md b/packages/parser/CHANGELOG.md index 3ed7f17edcf1..78926d846a92 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.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) + + +### Bug Fixes + +* **typescript-estree:** fix `async` identifier token typed as `Keyword` ([#681](https://github.com/typescript-eslint/typescript-eslint/issues/681)) ([6de19d3](https://github.com/typescript-eslint/typescript-eslint/commit/6de19d3)) + + + + + # [1.11.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.10.2...v1.11.0) (2019-06-23) diff --git a/packages/parser/package.json b/packages/parser/package.json index e683453843b1..cc6b85720b00 100644 --- a/packages/parser/package.json +++ b/packages/parser/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/parser", - "version": "1.11.0", + "version": "1.12.0", "description": "An ESLint custom parser which leverages TypeScript ESTree", "main": "dist/parser.js", "files": [ @@ -42,11 +42,11 @@ }, "dependencies": { "@types/eslint-visitor-keys": "^1.0.0", - "@typescript-eslint/experimental-utils": "1.11.0", - "@typescript-eslint/typescript-estree": "1.11.0", + "@typescript-eslint/experimental-utils": "1.12.0", + "@typescript-eslint/typescript-estree": "1.12.0", "eslint-visitor-keys": "^1.0.0" }, "devDependencies": { - "@typescript-eslint/shared-fixtures": "1.11.0" + "@typescript-eslint/shared-fixtures": "1.12.0" } } diff --git a/packages/shared-fixtures/CHANGELOG.md b/packages/shared-fixtures/CHANGELOG.md index 9cdeac709516..06c3ba92d001 100644 --- a/packages/shared-fixtures/CHANGELOG.md +++ b/packages/shared-fixtures/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.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) + + +### Bug Fixes + +* **typescript-estree:** fix `async` identifier token typed as `Keyword` ([#681](https://github.com/typescript-eslint/typescript-eslint/issues/681)) ([6de19d3](https://github.com/typescript-eslint/typescript-eslint/commit/6de19d3)) + + + + + # [1.11.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.10.2...v1.11.0) (2019-06-23) diff --git a/packages/shared-fixtures/package.json b/packages/shared-fixtures/package.json index 28caabd65f4b..080975e7bb28 100644 --- a/packages/shared-fixtures/package.json +++ b/packages/shared-fixtures/package.json @@ -1,5 +1,5 @@ { "name": "@typescript-eslint/shared-fixtures", - "version": "1.11.0", + "version": "1.12.0", "private": true } diff --git a/packages/typescript-estree/CHANGELOG.md b/packages/typescript-estree/CHANGELOG.md index 4f3c7cd49560..183ff3c672d4 100644 --- a/packages/typescript-estree/CHANGELOG.md +++ b/packages/typescript-estree/CHANGELOG.md @@ -3,6 +3,23 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.12.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.11.0...v1.12.0) (2019-07-12) + + +### Bug Fixes + +* **eslint-plugin:** handle `const;` ([#633](https://github.com/typescript-eslint/typescript-eslint/issues/633)) ([430d628](https://github.com/typescript-eslint/typescript-eslint/commit/430d628)), closes [#441](https://github.com/typescript-eslint/typescript-eslint/issues/441) +* **typescript-estree:** fix `async` identifier token typed as `Keyword` ([#681](https://github.com/typescript-eslint/typescript-eslint/issues/681)) ([6de19d3](https://github.com/typescript-eslint/typescript-eslint/commit/6de19d3)) + + +### Features + +* **eslint-plugin:** added new rule prefer-readonly ([#555](https://github.com/typescript-eslint/typescript-eslint/issues/555)) ([76b89a5](https://github.com/typescript-eslint/typescript-eslint/commit/76b89a5)) + + + + + # [1.11.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.10.2...v1.11.0) (2019-06-23) diff --git a/packages/typescript-estree/package.json b/packages/typescript-estree/package.json index 61ccbddf8588..33079d27f036 100644 --- a/packages/typescript-estree/package.json +++ b/packages/typescript-estree/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/typescript-estree", - "version": "1.11.0", + "version": "1.12.0", "description": "A parser that converts TypeScript source code into an ESTree compatible form", "main": "dist/parser.js", "types": "dist/parser.d.ts", @@ -46,6 +46,6 @@ }, "devDependencies": { "@babel/types": "^7.3.2", - "@typescript-eslint/shared-fixtures": "1.11.0" + "@typescript-eslint/shared-fixtures": "1.12.0" } }