From 6e7a7aef62e4a394ba81b405f34a7805cf82c002 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Mon, 18 Feb 2019 20:11:52 +0900 Subject: [PATCH 1/9] feat(eslint-plugin): add prefer-includes rule (fixes #284) --- .../docs/rules/prefer-includes.md | 64 +++ packages/eslint-plugin/package.json | 2 + .../src/rules/prefer-includes.ts | 212 +++++++++ .../tests/rules/prefer-includes.test.ts | 414 ++++++++++++++++++ .../eslint-plugin/typings/eslint-utils.d.ts | 178 ++++++++ packages/eslint-plugin/typings/ts-eslint.d.ts | 7 +- 6 files changed, 875 insertions(+), 2 deletions(-) create mode 100644 packages/eslint-plugin/docs/rules/prefer-includes.md create mode 100644 packages/eslint-plugin/src/rules/prefer-includes.ts create mode 100644 packages/eslint-plugin/tests/rules/prefer-includes.test.ts create mode 100644 packages/eslint-plugin/typings/eslint-utils.d.ts diff --git a/packages/eslint-plugin/docs/rules/prefer-includes.md b/packages/eslint-plugin/docs/rules/prefer-includes.md new file mode 100644 index 00000000000..26e70cc6eff --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-includes.md @@ -0,0 +1,64 @@ +# Enforce `includes` method over `indexOf` method (@typescript-eslint/prefer-includes) + +Until ES5, we were using `String#indexOf` method to check whether a string contains an arbitrary substring or not. +Until ES2015, we were using `Array#indexOf` method to check whether an array contains an arbitrary value or not. + +ES2015 has added `String#includes` and ES2016 has added `Array#includes`. +It makes code more understandable if we use those `includes` methods for the purpose. + +## Rule Details + +This rule is aimed at suggesting `includes` method if `indexOf` method was used to check whether an object contains an arbitrary value or not. + +If the receiver object of the `indexOf` method call has `includes` method and the two methods have the same parameters, this rule does suggestion. +There are such types: `String`, `Array`, `ReadonlyArray`, and typed arrays. + +Additionally, this rule reports the tests of simple regular expressions in favor of `String#includes`. + +Examples of **incorrect** code for this rule: + +```ts +let str: string +let array: any[] +let readonlyArray: ReadonlyArray +let typedArray: UInt8Array +let userDefined: { indexOf(x: any): number; includes(x: any): boolean } + +str.indexOf(value) !== -1 +array.indexOf(value) !== -1 +readonlyArray.indexOf(value) === -1 +typedArray.indexOf(value) > -1 +userDefined.indexOf(value) >= 0 + +// simple RegExp test +/foo/.test(str) +``` + +Examples of **correct** code for this rule: + +```ts +str.indexOf(value) !== -1 +let array: any[] +let readonlyArray: ReadonlyArray +let typedArray: UInt8Array +let userDefined: { + indexOf(x: any, fromIndex?: number): number + includes(x: any): boolean +} + +str.includes(value) +array.includes(value) +readonlyArray.includes(value) +typedArray.includes(value) + +// the two methods have different parameters. +userDefined.indexOf(value) >= 0 +``` + +## Options + +There is no option. + +## When Not To Use It + +If you don't want to suggest `includes`, you can safely turn this rule off. diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 5f3b1647ec2..3bd73ba1c59 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -37,6 +37,8 @@ "dependencies": { "@typescript-eslint/parser": "1.3.0", "@typescript-eslint/typescript-estree": "1.3.0", + "eslint-utils": "^1.3.1", + "regexpp": "^2.0.1", "requireindex": "^1.2.0", "tsutils": "^3.7.0" }, diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts new file mode 100644 index 00000000000..585be9661e2 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -0,0 +1,212 @@ +/** + * @fileoverview Enforce `includes` method over `indexOf` method. + * @author Toru Nagashima + */ + +import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { getStaticValue } from 'eslint-utils'; +import { AST as RegExpAST, parseRegExpLiteral } from "regexpp" +import ts from 'typescript'; +import { createRule, getParserServices } from '../util'; + +export default createRule({ + name: 'prefer-includes', + defaultOptions: [], + + meta: { + type: 'suggestion', + docs: { + description: 'Enforce `includes` method over `indexOf` method', + category: 'Best Practices', + recommended: false + }, + fixable: 'code', + messages: { + preferIncludes: "Use 'includes()' method instead.", + preferStringIncludes: + 'Use `String#includes()` method with a string instead.' + }, + schema: [] + }, + + create(context) { + const globalScope = context.getScope(); + const services = getParserServices(context); + const types = services.program.getTypeChecker(); + + function isNumber(node: TSESTree.Node, value: number): boolean { + const evaluated = getStaticValue(node, globalScope); + return evaluated !== null && evaluated.value === value; + } + + function isPositiveCheck(node: TSESTree.BinaryExpression): boolean { + switch (node.operator) { + case '!==': + case '!=': + case '>': + return isNumber(node.right, -1); + case '>=': + return isNumber(node.right, 0); + default: + return false; + } + } + function isNegativeCheck(node: TSESTree.BinaryExpression): boolean { + switch (node.operator) { + case '===': + case '==': + case '<=': + return isNumber(node.right, -1); + case '<': + return isNumber(node.right, 0); + default: + return false; + } + } + + function hasSameParameters( + nodeA: ts.Declaration, + nodeB: ts.Declaration + ): boolean { + if (!ts.isFunctionLike(nodeA) || !ts.isFunctionLike(nodeB)) { + return false; + } + + const paramsA = nodeA.parameters; + const paramsB = nodeB.parameters; + if (paramsA.length !== paramsB.length) { + return false; + } + + for (let i = 0; i < paramsA.length; ++i) { + const paramA = paramsA[i]; + const paramB = paramsB[i]; + + // Check name, type, and question token once. + if (paramA.getText() !== paramB.getText()) { + return false; + } + } + + return true; + } + + /** + * Parse a given node if it's a `RegExp` instance. + * @param node The node to parse. + */ + function parseRegExp( + node: TSESTree.Node + ): string | null { + const evaluated = getStaticValue(node, globalScope); + if (evaluated == null || !(evaluated.value instanceof RegExp)) { + return null; + } + + const { pattern, flags } = parseRegExpLiteral(evaluated.value); + if (pattern.alternatives.length !== 1 || flags.ignoreCase || flags.global) { + return null; + } + + // Check if it can determine a unique string. + const chars = pattern.alternatives[0].elements; + if (!chars.every(c => c.type === 'Character')) { + return null; + } + + // To string. + return String.fromCodePoint( + ...chars.map(c => (c as RegExpAST.Character).value) + ); + } + + return { + "BinaryExpression > CallExpression.left > MemberExpression.callee[property.name='indexOf'][computed=false]"( + node: TSESTree.MemberExpression + ): void { + // Check if the comparison is equivalent to `includes()`. + const callNode = node.parent as TSESTree.CallExpression; + const compareNode = callNode.parent as TSESTree.BinaryExpression; + const negative = isNegativeCheck(compareNode); + if (!negative && !isPositiveCheck(compareNode)) { + return; + } + + // Get the symbol of `indexOf` method. + const tsNode = services.esTreeNodeToTSNodeMap.get(node.property); + const indexofMethodSymbol = types.getSymbolAtLocation(tsNode); + if ( + indexofMethodSymbol == null || + indexofMethodSymbol.declarations.length === 0 + ) { + return; + } + + // Check if every declaration of `indexOf` method has `includes` method + // and the two methods have the same parameters. + for (const instanceofMethodDecl of indexofMethodSymbol.declarations) { + const typeDecl = instanceofMethodDecl.parent; + const type = types.getTypeAtLocation(typeDecl); + const includesMethodSymbol = type.getProperty('includes'); + if ( + includesMethodSymbol == null || + !includesMethodSymbol.declarations.some(includesMethodDecl => + hasSameParameters(includesMethodDecl, instanceofMethodDecl) + ) + ) { + return; + } + } + + // Report it. + context.report({ + node: compareNode, + messageId: 'preferIncludes', + *fix(fixer) { + if (negative) { + yield fixer.insertTextBefore(callNode, '!'); + } + yield fixer.replaceText(node.property, 'includes'); + yield fixer.removeRange([callNode.range[1], compareNode.range[1]]); + } + }); + }, + + // /bar/.test(foo) + 'CallExpression > MemberExpression.callee[property.name="test"][computed=false]'( + node: TSESTree.MemberExpression + ): void { + const callNode = node.parent as TSESTree.CallExpression; + const text = + callNode.arguments.length === 1 ? parseRegExp(node.object) : null; + if (text == null) { + return; + } + + context.report({ + node: callNode, + messageId: "preferStringIncludes", + *fix(fixer) { + const argNode = callNode.arguments[0]; + const needsParen = + argNode.type !== 'Literal' && + argNode.type !== 'TemplateLiteral' && + argNode.type !== 'Identifier' && + argNode.type !== 'MemberExpression' && + argNode.type !== 'CallExpression'; + + yield fixer.removeRange([callNode.range[0], argNode.range[0]]); + if (needsParen) { + yield fixer.insertTextBefore(argNode, '('); + yield fixer.insertTextAfter(argNode, ')'); + } + yield fixer.insertTextAfter( + argNode, + `.includes(${JSON.stringify(text)}` + ); + } + }); + } + }; + } +}); diff --git a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts new file mode 100644 index 00000000000..7ea69fabb83 --- /dev/null +++ b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts @@ -0,0 +1,414 @@ +import path from 'path'; +import rule from '../../src/rules/prefer-includes'; +import { RuleTester } from '../RuleTester'; + +const rootPath = path.join(process.cwd(), 'tests/fixtures/'); + +const ruleTester = new RuleTester({ + parser: '@typescript-eslint/parser', + parserOptions: { + tsconfigRootDir: rootPath, + project: './tsconfig.json' + } +}); + +ruleTester.run('prefer-includes', rule, { + valid: [ + ` + function f(a: string): void { + a.indexOf(b) + } + `, + ` + function f(a: string): void { + a.indexOf(b) + 0 + } + `, + ` + function f(a: string | {value: string}): void { + a.indexOf(b) !== -1 + } + `, + ` + type UserDefined = { + indexOf(x: any): number // don't have 'includes'. + } + function f(a: UserDefined): void { + a.indexOf(b) !== -1 + } + `, + ` + type UserDefined = { + indexOf(x: any, fromIndex?: number): number + includes(x: any): boolean // different parameters. + } + function f(a: UserDefined): void { + a.indexOf(b) !== -1 + } + `, + ` + type UserDefined = { + indexOf(x: any, fromIndex?: number): number + includes(x: any, fromIndex: number): boolean // different parameters. + } + function f(a: UserDefined): void { + a.indexOf(b) !== -1 + } + `, + ` + type UserDefined = { + indexOf(x: any, fromIndex?: number): number + includes: boolean // different type. + } + function f(a: UserDefined): void { + a.indexOf(b) !== -1 + } + `, + ` + function f(a: string): void { + /bar/i.test(a) + } + `, + ` + function f(a: string): void { + /ba[rz]/.test(a) + } + `, + ` + function f(a: string): void { + /foo|bar/.test(a) + } + ` + ], + invalid: [ + // positive + { + code: ` + function f(a: string): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: string): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: string): void { + a.indexOf(b) != -1 + } + `, + output: ` + function f(a: string): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: string): void { + a.indexOf(b) > -1 + } + `, + output: ` + function f(a: string): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: string): void { + a.indexOf(b) >= 0 + } + `, + output: ` + function f(a: string): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + + // negative + { + code: ` + function f(a: string): void { + a.indexOf(b) === -1 + } + `, + output: ` + function f(a: string): void { + !a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: string): void { + a.indexOf(b) == -1 + } + `, + output: ` + function f(a: string): void { + !a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: string): void { + a.indexOf(b) <= -1 + } + `, + output: ` + function f(a: string): void { + !a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: string): void { + a.indexOf(b) < 0 + } + `, + output: ` + function f(a: string): void { + !a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + + // RegExp#test + { + code: ` + function f(a: string): void { + /bar/.test(a) + } + `, + output: ` + function f(a: string): void { + a.includes("bar") + } + `, + errors: [{ messageId: 'preferStringIncludes' }] + }, + { + code: ` + const pattern = new RegExp("bar") + function f(a: string): void { + pattern.test(a) + } + `, + output: ` + const pattern = new RegExp("bar") + function f(a: string): void { + a.includes("bar") + } + `, + errors: [{ messageId: 'preferStringIncludes' }] + }, + + // type variation + { + code: ` + function f(a: any[]): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: any[]): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: ReadonlyArray): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: ReadonlyArray): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Int8Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Int8Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Int16Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Int16Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Int32Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Int32Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Uint8Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Uint8Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Uint16Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Uint16Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Uint32Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Uint32Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Float32Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Float32Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Float64Array): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Float64Array): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: T[] | ReadonlyArray): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: T[] | ReadonlyArray): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array>(a: U): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f | Int8Array | Uint8Array | Int16Array | Uint16Array | Int32Array | Uint32Array | Float32Array | Float64Array>(a: U): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + type UserDefined = { + indexOf(x: any): number + includes(x: any): boolean + } + function f(a: UserDefined): void { + a.indexOf(b) !== -1 + } + `, + output: ` + type UserDefined = { + indexOf(x: any): number + includes(x: any): boolean + } + function f(a: UserDefined): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + }, + { + code: ` + function f(a: Readonly): void { + a.indexOf(b) !== -1 + } + `, + output: ` + function f(a: Readonly): void { + a.includes(b) + } + `, + errors: [{ messageId: 'preferIncludes' }] + } + ] +}); diff --git a/packages/eslint-plugin/typings/eslint-utils.d.ts b/packages/eslint-plugin/typings/eslint-utils.d.ts new file mode 100644 index 00000000000..f246c2470bc --- /dev/null +++ b/packages/eslint-plugin/typings/eslint-utils.d.ts @@ -0,0 +1,178 @@ +declare module 'eslint-utils' { + import { TSESTree } from '@typescript-eslint/typescript-estree'; + import { Scope, SourceCode } from 'ts-eslint'; + + export function getFunctionHeadLocation( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression, + sourceCode: SourceCode + ): TSESTree.SourceLocation; + + export function getFunctionNameWithKind( + node: + | TSESTree.FunctionDeclaration + | TSESTree.FunctionExpression + | TSESTree.ArrowFunctionExpression + ): string; + + export function getPropertyName( + node: + | TSESTree.MemberExpression + | TSESTree.Property + | TSESTree.MethodDefinition, + initialScope?: Scope.Scope + ): string | null; + + export function getStaticValue( + node: TSESTree.Node, + initialScope?: Scope.Scope + ): { value: any } | null; + + export function getStringIfConstant( + node: TSESTree.Node, + initialScope?: Scope.Scope + ): string | null; + + export function hasSideEffect( + node: TSESTree.Node, + sourceCode: SourceCode, + options?: { + considerGetters?: boolean; + considerImplicitTypeConversion?: boolean; + } + ): boolean; + + export function isParenthesized( + node: TSESTree.Node, + sourceCode: SourceCode + ): boolean; + + export class PatternMatcher { + constructor(pattern: RegExp, options?: { escaped?: boolean }); + execAll(str: string): IterableIterator; + test(str: string): boolean; + } + + export function findVariable( + initialScope: Scope.Scope, + name: string + ): Scope.Variable | null; + + export function getInnermostScope( + initialScope: Scope.Scope, + node: TSESTree.Node + ): Scope.Scope; + + export class ReferenceTracker { + static readonly READ: unique symbol; + static readonly CALL: unique symbol; + static readonly CONSTRUCT: unique symbol; + + constructor( + globalScope: Scope.Scope, + options?: { + mode: 'strict' | 'legacy'; + globalObjectNames: ReadonlyArray; + } + ); + + iterateGlobalReferences( + traceMap: ReferenceTracker.TraceMap + ): IterableIterator>; + iterateCjsReferences( + traceMap: ReferenceTracker.TraceMap + ): IterableIterator>; + iterateEsmReferences( + traceMap: ReferenceTracker.TraceMap + ): IterableIterator>; + } + + export namespace ReferenceTracker { + export type READ = typeof ReferenceTracker.READ; + export type CALL = typeof ReferenceTracker.READ; + export type CONSTRUCT = typeof ReferenceTracker.READ; + export type ReferenceType = READ | CALL | CONSTRUCT; + export type TraceMap = Record>; + export interface TraceMapElement { + [ReferenceTracker.READ]?: T; + [ReferenceTracker.CALL]?: T; + [ReferenceTracker.CONSTRUCT]?: T; + [key: string]: TraceMapElement; + } + export interface FoundReference { + node: TSESTree.Node; + path: ReadonlyArray; + type: ReferenceType; + entry: T; + } + } + + export function isArrowToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotArrowToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isClosingBraceToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotClosingBraceToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isClosingBracketToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotClosingBracketToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isClosingParenToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotClosingParenToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isColonToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotColonToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isCommaToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotCommaToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isCommentToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotCommentToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isOpeningBraceToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotOpeningBraceToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isOpeningBracketToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotOpeningBracketToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isOpeningParenToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotOpeningParenToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isSemicolonToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; + export function isNotSemicolonToken( + token: TSESTree.Token | TSESTree.Comment + ): boolean; +} diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts index 9ab6ff35e21..401d3375e11 100644 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ b/packages/eslint-plugin/typings/ts-eslint.d.ts @@ -297,7 +297,9 @@ declare module 'ts-eslint' { replaceTextRange(range: AST.Range, text: string): RuleFix; } - type ReportFixFunction = (fixer: RuleFixer) => null | RuleFix | RuleFix[]; + type ReportFixFunction = ( + fixer: RuleFixer + ) => null | RuleFix | Iterable; interface ReportDescriptor { /** @@ -530,7 +532,8 @@ declare module 'ts-eslint' { RuleListener, RuleMetaData, RuleMetaDataDocs, - Scope + Scope, + SourceCode }; export default RuleModule; } From 785cb63a1910d2faf213fa66c8c66c2e607dca8a Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Mon, 18 Feb 2019 21:26:02 +0900 Subject: [PATCH 2/9] fix formating errors --- .../docs/rules/prefer-includes.md | 47 +++++++++---------- .../src/rules/prefer-includes.ts | 14 +++--- 2 files changed, 31 insertions(+), 30 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-includes.md b/packages/eslint-plugin/docs/rules/prefer-includes.md index 26e70cc6eff..e706aa2ccec 100644 --- a/packages/eslint-plugin/docs/rules/prefer-includes.md +++ b/packages/eslint-plugin/docs/rules/prefer-includes.md @@ -18,41 +18,40 @@ Additionally, this rule reports the tests of simple regular expressions in favor Examples of **incorrect** code for this rule: ```ts -let str: string -let array: any[] -let readonlyArray: ReadonlyArray -let typedArray: UInt8Array -let userDefined: { indexOf(x: any): number; includes(x: any): boolean } - -str.indexOf(value) !== -1 -array.indexOf(value) !== -1 -readonlyArray.indexOf(value) === -1 -typedArray.indexOf(value) > -1 -userDefined.indexOf(value) >= 0 +let str: string; +let array: any[]; +let readonlyArray: ReadonlyArray; +let typedArray: UInt8Array; +let userDefined: { indexOf(x: any): number; includes(x: any): boolean }; + +str.indexOf(value) !== -1; +array.indexOf(value) !== -1; +readonlyArray.indexOf(value) === -1; +typedArray.indexOf(value) > -1; +userDefined.indexOf(value) >= 0; // simple RegExp test -/foo/.test(str) +/foo/.test(str); ``` Examples of **correct** code for this rule: ```ts -str.indexOf(value) !== -1 -let array: any[] -let readonlyArray: ReadonlyArray -let typedArray: UInt8Array +let array: any[]; +let readonlyArray: ReadonlyArray; +let typedArray: UInt8Array; let userDefined: { - indexOf(x: any, fromIndex?: number): number - includes(x: any): boolean -} + indexOf(x: any, fromIndex?: number): number; + includes(x: any): boolean; +}; -str.includes(value) -array.includes(value) -readonlyArray.includes(value) -typedArray.includes(value) +str.includes(value); +array.includes(value); +readonlyArray.includes(value); +typedArray.includes(value); // the two methods have different parameters. -userDefined.indexOf(value) >= 0 +userDefined.indexOf(value) >= 0; ``` ## Options diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 585be9661e2..464808a8837 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -5,7 +5,7 @@ import { TSESTree } from '@typescript-eslint/typescript-estree'; import { getStaticValue } from 'eslint-utils'; -import { AST as RegExpAST, parseRegExpLiteral } from "regexpp" +import { AST as RegExpAST, parseRegExpLiteral } from 'regexpp'; import ts from 'typescript'; import { createRule, getParserServices } from '../util'; @@ -95,16 +95,18 @@ export default createRule({ * Parse a given node if it's a `RegExp` instance. * @param node The node to parse. */ - function parseRegExp( - node: TSESTree.Node - ): string | null { + function parseRegExp(node: TSESTree.Node): string | null { const evaluated = getStaticValue(node, globalScope); if (evaluated == null || !(evaluated.value instanceof RegExp)) { return null; } const { pattern, flags } = parseRegExpLiteral(evaluated.value); - if (pattern.alternatives.length !== 1 || flags.ignoreCase || flags.global) { + if ( + pattern.alternatives.length !== 1 || + flags.ignoreCase || + flags.global + ) { return null; } @@ -185,7 +187,7 @@ export default createRule({ context.report({ node: callNode, - messageId: "preferStringIncludes", + messageId: 'preferStringIncludes', *fix(fixer) { const argNode = callNode.arguments[0]; const needsParen = From 521a91b43e57a6c6a734693af8bc7ea7b038d253 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Mon, 18 Feb 2019 21:51:14 +0900 Subject: [PATCH 3/9] =?UTF-8?q?add=20some=20tests=20for=20coverage=20?= =?UTF-8?q?=F0=9F=92=AF?= MIME-Version: 1.0 Content-Type: text/plain; charset=UTF-8 Content-Transfer-Encoding: 8bit --- .../tests/rules/prefer-includes.test.ts | 25 +++++++++++++++++++ 1 file changed, 25 insertions(+) diff --git a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts index 7ea69fabb83..249c2512fc3 100644 --- a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts @@ -78,7 +78,17 @@ ruleTester.run('prefer-includes', rule, { function f(a: string): void { /foo|bar/.test(a) } + `, + ` + function f(a: string): void { + /bar/.test() + } + `, ` + function f(a: string): void { + something.test(a) + } + `, ], invalid: [ // positive @@ -218,6 +228,21 @@ ruleTester.run('prefer-includes', rule, { `, errors: [{ messageId: 'preferStringIncludes' }] }, + { + code: ` + const pattern = /bar/ + function f(a: string, b: string): void { + pattern.test(a + b) + } + `, + output: ` + const pattern = /bar/ + function f(a: string, b: string): void { + (a + b).includes("bar") + } + `, + errors: [{ messageId: 'preferStringIncludes' }] + }, // type variation { From e7ed0ba753caeb970a9139304970be685beb5d74 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Mon, 18 Feb 2019 21:58:13 +0900 Subject: [PATCH 4/9] fix formatting error --- packages/eslint-plugin/tests/rules/prefer-includes.test.ts | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts index 249c2512fc3..a51d83c626e 100644 --- a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts @@ -88,7 +88,7 @@ ruleTester.run('prefer-includes', rule, { function f(a: string): void { something.test(a) } - `, + ` ], invalid: [ // positive From dee11becf31233642feb26d2f71593f374de765a Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Wed, 20 Feb 2019 15:20:42 +0900 Subject: [PATCH 5/9] fix format as following master branch --- .../src/rules/prefer-includes.ts | 26 +++---- .../tests/rules/prefer-includes.test.ts | 60 ++++++++-------- .../eslint-plugin/typings/eslint-utils.d.ts | 70 +++++++++---------- packages/eslint-plugin/typings/ts-eslint.d.ts | 2 +- 4 files changed, 79 insertions(+), 79 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 464808a8837..2f8e6f25031 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -18,15 +18,15 @@ export default createRule({ docs: { description: 'Enforce `includes` method over `indexOf` method', category: 'Best Practices', - recommended: false + recommended: false, }, fixable: 'code', messages: { preferIncludes: "Use 'includes()' method instead.", preferStringIncludes: - 'Use `String#includes()` method with a string instead.' + 'Use `String#includes()` method with a string instead.', }, - schema: [] + schema: [], }, create(context) { @@ -66,7 +66,7 @@ export default createRule({ function hasSameParameters( nodeA: ts.Declaration, - nodeB: ts.Declaration + nodeB: ts.Declaration, ): boolean { if (!ts.isFunctionLike(nodeA) || !ts.isFunctionLike(nodeB)) { return false; @@ -118,13 +118,13 @@ export default createRule({ // To string. return String.fromCodePoint( - ...chars.map(c => (c as RegExpAST.Character).value) + ...chars.map(c => (c as RegExpAST.Character).value), ); } return { "BinaryExpression > CallExpression.left > MemberExpression.callee[property.name='indexOf'][computed=false]"( - node: TSESTree.MemberExpression + node: TSESTree.MemberExpression, ): void { // Check if the comparison is equivalent to `includes()`. const callNode = node.parent as TSESTree.CallExpression; @@ -153,7 +153,7 @@ export default createRule({ if ( includesMethodSymbol == null || !includesMethodSymbol.declarations.some(includesMethodDecl => - hasSameParameters(includesMethodDecl, instanceofMethodDecl) + hasSameParameters(includesMethodDecl, instanceofMethodDecl), ) ) { return; @@ -170,13 +170,13 @@ export default createRule({ } yield fixer.replaceText(node.property, 'includes'); yield fixer.removeRange([callNode.range[1], compareNode.range[1]]); - } + }, }); }, // /bar/.test(foo) 'CallExpression > MemberExpression.callee[property.name="test"][computed=false]'( - node: TSESTree.MemberExpression + node: TSESTree.MemberExpression, ): void { const callNode = node.parent as TSESTree.CallExpression; const text = @@ -204,11 +204,11 @@ export default createRule({ } yield fixer.insertTextAfter( argNode, - `.includes(${JSON.stringify(text)}` + `.includes(${JSON.stringify(text)}`, ); - } + }, }); - } + }, }; - } + }, }); diff --git a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts index a51d83c626e..f754c487521 100644 --- a/packages/eslint-plugin/tests/rules/prefer-includes.test.ts +++ b/packages/eslint-plugin/tests/rules/prefer-includes.test.ts @@ -8,8 +8,8 @@ const ruleTester = new RuleTester({ parser: '@typescript-eslint/parser', parserOptions: { tsconfigRootDir: rootPath, - project: './tsconfig.json' - } + project: './tsconfig.json', + }, }); ruleTester.run('prefer-includes', rule, { @@ -88,7 +88,7 @@ ruleTester.run('prefer-includes', rule, { function f(a: string): void { something.test(a) } - ` + `, ], invalid: [ // positive @@ -103,7 +103,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -116,7 +116,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -129,7 +129,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -142,7 +142,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, // negative @@ -157,7 +157,7 @@ ruleTester.run('prefer-includes', rule, { !a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -170,7 +170,7 @@ ruleTester.run('prefer-includes', rule, { !a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -183,7 +183,7 @@ ruleTester.run('prefer-includes', rule, { !a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -196,7 +196,7 @@ ruleTester.run('prefer-includes', rule, { !a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, // RegExp#test @@ -211,7 +211,7 @@ ruleTester.run('prefer-includes', rule, { a.includes("bar") } `, - errors: [{ messageId: 'preferStringIncludes' }] + errors: [{ messageId: 'preferStringIncludes' }], }, { code: ` @@ -226,7 +226,7 @@ ruleTester.run('prefer-includes', rule, { a.includes("bar") } `, - errors: [{ messageId: 'preferStringIncludes' }] + errors: [{ messageId: 'preferStringIncludes' }], }, { code: ` @@ -241,7 +241,7 @@ ruleTester.run('prefer-includes', rule, { (a + b).includes("bar") } `, - errors: [{ messageId: 'preferStringIncludes' }] + errors: [{ messageId: 'preferStringIncludes' }], }, // type variation @@ -256,7 +256,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -269,7 +269,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -282,7 +282,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -295,7 +295,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -308,7 +308,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -321,7 +321,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -334,7 +334,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -347,7 +347,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -360,7 +360,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -373,7 +373,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -386,7 +386,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -399,7 +399,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -420,7 +420,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] + errors: [{ messageId: 'preferIncludes' }], }, { code: ` @@ -433,7 +433,7 @@ ruleTester.run('prefer-includes', rule, { a.includes(b) } `, - errors: [{ messageId: 'preferIncludes' }] - } - ] + errors: [{ messageId: 'preferIncludes' }], + }, + ], }); diff --git a/packages/eslint-plugin/typings/eslint-utils.d.ts b/packages/eslint-plugin/typings/eslint-utils.d.ts index f246c2470bc..d926229b00e 100644 --- a/packages/eslint-plugin/typings/eslint-utils.d.ts +++ b/packages/eslint-plugin/typings/eslint-utils.d.ts @@ -7,14 +7,14 @@ declare module 'eslint-utils' { | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression | TSESTree.ArrowFunctionExpression, - sourceCode: SourceCode + sourceCode: SourceCode, ): TSESTree.SourceLocation; export function getFunctionNameWithKind( node: | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression - | TSESTree.ArrowFunctionExpression + | TSESTree.ArrowFunctionExpression, ): string; export function getPropertyName( @@ -22,17 +22,17 @@ declare module 'eslint-utils' { | TSESTree.MemberExpression | TSESTree.Property | TSESTree.MethodDefinition, - initialScope?: Scope.Scope + initialScope?: Scope.Scope, ): string | null; export function getStaticValue( node: TSESTree.Node, - initialScope?: Scope.Scope + initialScope?: Scope.Scope, ): { value: any } | null; export function getStringIfConstant( node: TSESTree.Node, - initialScope?: Scope.Scope + initialScope?: Scope.Scope, ): string | null; export function hasSideEffect( @@ -41,12 +41,12 @@ declare module 'eslint-utils' { options?: { considerGetters?: boolean; considerImplicitTypeConversion?: boolean; - } + }, ): boolean; export function isParenthesized( node: TSESTree.Node, - sourceCode: SourceCode + sourceCode: SourceCode, ): boolean; export class PatternMatcher { @@ -57,12 +57,12 @@ declare module 'eslint-utils' { export function findVariable( initialScope: Scope.Scope, - name: string + name: string, ): Scope.Variable | null; export function getInnermostScope( initialScope: Scope.Scope, - node: TSESTree.Node + node: TSESTree.Node, ): Scope.Scope; export class ReferenceTracker { @@ -75,17 +75,17 @@ declare module 'eslint-utils' { options?: { mode: 'strict' | 'legacy'; globalObjectNames: ReadonlyArray; - } + }, ); iterateGlobalReferences( - traceMap: ReferenceTracker.TraceMap + traceMap: ReferenceTracker.TraceMap, ): IterableIterator>; iterateCjsReferences( - traceMap: ReferenceTracker.TraceMap + traceMap: ReferenceTracker.TraceMap, ): IterableIterator>; iterateEsmReferences( - traceMap: ReferenceTracker.TraceMap + traceMap: ReferenceTracker.TraceMap, ): IterableIterator>; } @@ -110,69 +110,69 @@ declare module 'eslint-utils' { } export function isArrowToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotArrowToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isClosingBraceToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotClosingBraceToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isClosingBracketToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotClosingBracketToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isClosingParenToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotClosingParenToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isColonToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotColonToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isCommaToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotCommaToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isCommentToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotCommentToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isOpeningBraceToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotOpeningBraceToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isOpeningBracketToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotOpeningBracketToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isOpeningParenToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotOpeningParenToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isSemicolonToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; export function isNotSemicolonToken( - token: TSESTree.Token | TSESTree.Comment + token: TSESTree.Token | TSESTree.Comment, ): boolean; } diff --git a/packages/eslint-plugin/typings/ts-eslint.d.ts b/packages/eslint-plugin/typings/ts-eslint.d.ts index e0f4206f8e9..9ee605f8efe 100644 --- a/packages/eslint-plugin/typings/ts-eslint.d.ts +++ b/packages/eslint-plugin/typings/ts-eslint.d.ts @@ -298,7 +298,7 @@ declare module 'ts-eslint' { } type ReportFixFunction = ( - fixer: RuleFixer + fixer: RuleFixer, ) => null | RuleFix | Iterable; interface ReportDescriptor { From c2d04f7f9d3785861e53106d33e51e82058d1a19 Mon Sep 17 00:00:00 2001 From: Sindre Sorhus Date: Wed, 20 Feb 2019 16:19:53 +0900 Subject: [PATCH 6/9] Update packages/eslint-plugin/docs/rules/prefer-includes.md Co-Authored-By: mysticatea --- packages/eslint-plugin/docs/rules/prefer-includes.md | 2 +- 1 file changed, 1 insertion(+), 1 deletion(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-includes.md b/packages/eslint-plugin/docs/rules/prefer-includes.md index e706aa2ccec..38ed73dd82a 100644 --- a/packages/eslint-plugin/docs/rules/prefer-includes.md +++ b/packages/eslint-plugin/docs/rules/prefer-includes.md @@ -56,7 +56,7 @@ userDefined.indexOf(value) >= 0; ## Options -There is no option. +There are no options. ## When Not To Use It From 6af8cfdfa003e5ba09840f42a804a96cfeca0a29 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Sun, 24 Feb 2019 02:00:38 +0900 Subject: [PATCH 7/9] improve examples --- packages/eslint-plugin/docs/rules/prefer-includes.md | 12 ++++++++++-- 1 file changed, 10 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-includes.md b/packages/eslint-plugin/docs/rules/prefer-includes.md index 38ed73dd82a..432adb977db 100644 --- a/packages/eslint-plugin/docs/rules/prefer-includes.md +++ b/packages/eslint-plugin/docs/rules/prefer-includes.md @@ -22,7 +22,10 @@ let str: string; let array: any[]; let readonlyArray: ReadonlyArray; let typedArray: UInt8Array; -let userDefined: { indexOf(x: any): number; includes(x: any): boolean }; +let userDefined: { + indexOf(x: any): number; + includes(x: any): boolean +}; str.indexOf(value) !== -1; array.indexOf(value) !== -1; @@ -41,6 +44,10 @@ let array: any[]; let readonlyArray: ReadonlyArray; let typedArray: UInt8Array; let userDefined: { + indexOf(x: any): number; + includes(x: any): boolean +}; +let mismatchExample: { indexOf(x: any, fromIndex?: number): number; includes(x: any): boolean; }; @@ -49,9 +56,10 @@ str.includes(value); array.includes(value); readonlyArray.includes(value); typedArray.includes(value); +userDefined.includes(value); // the two methods have different parameters. -userDefined.indexOf(value) >= 0; +mismatchExample.indexOf(value) >= 0; ``` ## Options From 6d86472750cb4a50f7a4d605c43d2a8faeee6e5c Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Sun, 24 Feb 2019 02:03:07 +0900 Subject: [PATCH 8/9] remove comment --- packages/eslint-plugin/src/rules/prefer-includes.ts | 5 ----- 1 file changed, 5 deletions(-) diff --git a/packages/eslint-plugin/src/rules/prefer-includes.ts b/packages/eslint-plugin/src/rules/prefer-includes.ts index 2f8e6f25031..f7b3eb153ee 100644 --- a/packages/eslint-plugin/src/rules/prefer-includes.ts +++ b/packages/eslint-plugin/src/rules/prefer-includes.ts @@ -1,8 +1,3 @@ -/** - * @fileoverview Enforce `includes` method over `indexOf` method. - * @author Toru Nagashima - */ - import { TSESTree } from '@typescript-eslint/typescript-estree'; import { getStaticValue } from 'eslint-utils'; import { AST as RegExpAST, parseRegExpLiteral } from 'regexpp'; From a7b6b00afdb018a41569b8e4e4d8a9b004d722d7 Mon Sep 17 00:00:00 2001 From: Toru Nagashima Date: Sun, 24 Feb 2019 02:05:18 +0900 Subject: [PATCH 9/9] fix format --- packages/eslint-plugin/docs/rules/prefer-includes.md | 4 ++-- 1 file changed, 2 insertions(+), 2 deletions(-) diff --git a/packages/eslint-plugin/docs/rules/prefer-includes.md b/packages/eslint-plugin/docs/rules/prefer-includes.md index 432adb977db..1ea37102b1d 100644 --- a/packages/eslint-plugin/docs/rules/prefer-includes.md +++ b/packages/eslint-plugin/docs/rules/prefer-includes.md @@ -24,7 +24,7 @@ let readonlyArray: ReadonlyArray; let typedArray: UInt8Array; let userDefined: { indexOf(x: any): number; - includes(x: any): boolean + includes(x: any): boolean; }; str.indexOf(value) !== -1; @@ -45,7 +45,7 @@ let readonlyArray: ReadonlyArray; let typedArray: UInt8Array; let userDefined: { indexOf(x: any): number; - includes(x: any): boolean + includes(x: any): boolean; }; let mismatchExample: { indexOf(x: any, fromIndex?: number): number;