diff --git a/.eslintrc.json b/.eslintrc.json index e3abc256901..818b90dfab5 100644 --- a/.eslintrc.json +++ b/.eslintrc.json @@ -25,7 +25,8 @@ "sourceType": "module", "ecmaFeatures": { "jsx": false - } + }, + "project": "./tsconfig.base.json" }, "overrides": [ { diff --git a/.github/PULL_REQUEST_TEMPLATE/standard.md b/.github/PULL_REQUEST_TEMPLATE/standard.md new file mode 100644 index 00000000000..fb7952bc768 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/standard.md @@ -0,0 +1,9 @@ +--- +name: 'Standard' +about: Standard Pull Request Template +title: '' +labels: '' +assignees: '' +--- + + diff --git a/.github/PULL_REQUEST_TEMPLATE/typescript_version_upgrade.md b/.github/PULL_REQUEST_TEMPLATE/typescript_version_upgrade.md new file mode 100644 index 00000000000..d51581780f2 --- /dev/null +++ b/.github/PULL_REQUEST_TEMPLATE/typescript_version_upgrade.md @@ -0,0 +1,18 @@ +--- +name: 'TypeScript Version Upgrade' +about: Used when upgrading the supported version of TypeScript +title: '' +labels: '' +assignees: '' +--- + +**Please complete the following:** + +TypeScript version added by this PR: `{{ INSERT_TYPESCRIPT_VERSION }}` + +- [ ] I have updated the devDependency range in the root package.json +- [ ] I have updated the range value in `packages/typescript-estree/src/parser.ts` +- [ ] I have run the existing tests to make sure they still pass, or made any required updates +- [ ] I have added new tests for the features introduced in this newer version of TypeScript +- New feature tests added: + - ... diff --git a/CHANGELOG.md b/CHANGELOG.md index 19e950868dc..7eb1d41a284 100644 --- a/CHANGELOG.md +++ b/CHANGELOG.md @@ -3,6 +3,35 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.6.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.5.0...v1.6.0) (2019-04-03) + +### Bug Fixes + +- **eslint-plugin:** explicit-function-return-type: ensure class arrow methods are validated ([#377](https://github.com/typescript-eslint/typescript-eslint/issues/377)) ([643a223](https://github.com/typescript-eslint/typescript-eslint/commit/643a223)), closes [#348](https://github.com/typescript-eslint/typescript-eslint/issues/348) +- **eslint-plugin:** Fix `allowExpressions` false positives in explicit-function-return-type and incorrect documentation ([#388](https://github.com/typescript-eslint/typescript-eslint/issues/388)) ([f29d1c9](https://github.com/typescript-eslint/typescript-eslint/commit/f29d1c9)), closes [#387](https://github.com/typescript-eslint/typescript-eslint/issues/387) +- **eslint-plugin:** member-naming false flagging constructors ([#376](https://github.com/typescript-eslint/typescript-eslint/issues/376)) ([ad0f2be](https://github.com/typescript-eslint/typescript-eslint/commit/ad0f2be)), closes [#359](https://github.com/typescript-eslint/typescript-eslint/issues/359) +- **eslint-plugin:** no-type-alias: fix typeof alias erroring ([#380](https://github.com/typescript-eslint/typescript-eslint/issues/380)) ([cebcfe6](https://github.com/typescript-eslint/typescript-eslint/commit/cebcfe6)) +- **parser:** Make eslint traverse enum id ([#383](https://github.com/typescript-eslint/typescript-eslint/issues/383)) ([492b737](https://github.com/typescript-eslint/typescript-eslint/commit/492b737)) +- **typescript-estree:** add ExportDefaultDeclaration to union DeclarationStatement ([#378](https://github.com/typescript-eslint/typescript-eslint/issues/378)) ([bf04398](https://github.com/typescript-eslint/typescript-eslint/commit/bf04398)) + +### Features + +- change TypeScript version range to >=3.2.1 <3.5.0 ([#399](https://github.com/typescript-eslint/typescript-eslint/issues/399)) ([a4f95d3](https://github.com/typescript-eslint/typescript-eslint/commit/a4f95d3)) +- **eslint-plugin:** allow explicit variable type with arrow functions ([#260](https://github.com/typescript-eslint/typescript-eslint/issues/260)) ([bea6b92](https://github.com/typescript-eslint/typescript-eslint/commit/bea6b92)), closes [#149](https://github.com/typescript-eslint/typescript-eslint/issues/149) + +# [1.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.4.2...v1.5.0) (2019-03-20) + +### Bug Fixes + +- **eslint-plugin:** [interface-name-prefix] correct error message in always mode ([#333](https://github.com/typescript-eslint/typescript-eslint/issues/333)) ([097262f](https://github.com/typescript-eslint/typescript-eslint/commit/097262f)) +- **eslint-plugin:** fix false positives for adjacent-overload-signatures regarding computed property names ([#340](https://github.com/typescript-eslint/typescript-eslint/issues/340)) ([f6e5118](https://github.com/typescript-eslint/typescript-eslint/commit/f6e5118)) +- **eslint-plugin:** fix incorrect rule name ([#357](https://github.com/typescript-eslint/typescript-eslint/issues/357)) ([0a5146b](https://github.com/typescript-eslint/typescript-eslint/commit/0a5146b)) +- **typescript-estree:** only call watch callback on new files ([#367](https://github.com/typescript-eslint/typescript-eslint/issues/367)) ([0ef07c4](https://github.com/typescript-eslint/typescript-eslint/commit/0ef07c4)) + +### Features + +- **eslint-plugin:** Add unified-signature rule ([#178](https://github.com/typescript-eslint/typescript-eslint/issues/178)) ([6ffaa0b](https://github.com/typescript-eslint/typescript-eslint/commit/6ffaa0b)) + ## [1.4.2](https://github.com/typescript-eslint/typescript-eslint/compare/v1.4.1...v1.4.2) (2019-02-25) ### Bug Fixes diff --git a/README.md b/README.md index 0aef0f23d0b..0a5267f1097 100644 --- a/README.md +++ b/README.md @@ -44,7 +44,7 @@ The `canary` (latest master) version is: We will always endeavor to support the latest stable version of TypeScript. Sometimes, but not always, changes in TypeScript will not require breaking changes in this project, and so we are able to support more than one version of TypeScript. -**The version range of TypeScript currently supported by this parser is `>=3.2.1 <3.4.0`.** +**The version range of TypeScript currently supported by this parser is `>=3.2.1 <3.5.0`.** This is reflected in the `devDependency` requirement within the package.json file, and it is what the tests will be run against. We have an open `peerDependency` requirement in order to allow for experimentation on newer/beta versions of TypeScript. diff --git a/lerna.json b/lerna.json index 06b203850da..f7243f760b6 100644 --- a/lerna.json +++ b/lerna.json @@ -1,5 +1,5 @@ { - "version": "1.4.2", + "version": "1.6.0", "npmClient": "yarn", "useWorkspaces": true, "stream": true diff --git a/package.json b/package.json index 57a5e6124b0..87f47a22a52 100644 --- a/package.json +++ b/package.json @@ -74,6 +74,6 @@ "ts-jest": "^24.0.0", "ts-node": "^8.0.1", "tslint": "^5.11.0", - "typescript": ">=3.2.1 <3.4.0" + "typescript": ">=3.2.1 <3.5.0" } } diff --git a/packages/eslint-plugin-tslint/CHANGELOG.md b/packages/eslint-plugin-tslint/CHANGELOG.md index eaa8acd6892..a932eb1dc8c 100644 --- a/packages/eslint-plugin-tslint/CHANGELOG.md +++ b/packages/eslint-plugin-tslint/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.6.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.5.0...v1.6.0) (2019-04-03) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + +# [1.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.4.2...v1.5.0) (2019-03-20) + +**Note:** Version bump only for package @typescript-eslint/eslint-plugin-tslint + ## [1.4.2](https://github.com/typescript-eslint/typescript-eslint/compare/v1.4.1...v1.4.2) (2019-02-25) **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 955573693fe..c8279102b38 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.4.2", + "version": "1.6.0", "main": "dist/index.js", "typings": "src/index.ts", "description": "TSLint wrapper plugin for ESLint", @@ -35,6 +35,6 @@ "devDependencies": { "@types/eslint": "^4.16.3", "@types/lodash.memoize": "^4.1.4", - "@typescript-eslint/parser": "1.4.2" + "@typescript-eslint/parser": "1.6.0" } } diff --git a/packages/eslint-plugin/CHANGELOG.md b/packages/eslint-plugin/CHANGELOG.md index c4c8d82eb58..628a911c334 100644 --- a/packages/eslint-plugin/CHANGELOG.md +++ b/packages/eslint-plugin/CHANGELOG.md @@ -3,6 +3,32 @@ All notable changes to this project will be documented in this file. See [Conventional Commits](https://conventionalcommits.org) for commit guidelines. +# [1.6.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.5.0...v1.6.0) (2019-04-03) + +### Bug Fixes + +- **eslint-plugin:** explicit-function-return-type: ensure class arrow methods are validated ([#377](https://github.com/typescript-eslint/typescript-eslint/issues/377)) ([643a223](https://github.com/typescript-eslint/typescript-eslint/commit/643a223)), closes [#348](https://github.com/typescript-eslint/typescript-eslint/issues/348) +- **eslint-plugin:** Fix `allowExpressions` false positives in explicit-function-return-type and incorrect documentation ([#388](https://github.com/typescript-eslint/typescript-eslint/issues/388)) ([f29d1c9](https://github.com/typescript-eslint/typescript-eslint/commit/f29d1c9)), closes [#387](https://github.com/typescript-eslint/typescript-eslint/issues/387) +- **eslint-plugin:** member-naming false flagging constructors ([#376](https://github.com/typescript-eslint/typescript-eslint/issues/376)) ([ad0f2be](https://github.com/typescript-eslint/typescript-eslint/commit/ad0f2be)), closes [#359](https://github.com/typescript-eslint/typescript-eslint/issues/359) +- **eslint-plugin:** no-type-alias: fix typeof alias erroring ([#380](https://github.com/typescript-eslint/typescript-eslint/issues/380)) ([cebcfe6](https://github.com/typescript-eslint/typescript-eslint/commit/cebcfe6)) + +### Features + +- change TypeScript version range to >=3.2.1 <3.5.0 ([#399](https://github.com/typescript-eslint/typescript-eslint/issues/399)) ([a4f95d3](https://github.com/typescript-eslint/typescript-eslint/commit/a4f95d3)) +- **eslint-plugin:** allow explicit variable type with arrow functions ([#260](https://github.com/typescript-eslint/typescript-eslint/issues/260)) ([bea6b92](https://github.com/typescript-eslint/typescript-eslint/commit/bea6b92)), closes [#149](https://github.com/typescript-eslint/typescript-eslint/issues/149) + +# [1.5.0](https://github.com/typescript-eslint/typescript-eslint/compare/v1.4.2...v1.5.0) (2019-03-20) + +### Bug Fixes + +- **eslint-plugin:** [interface-name-prefix] correct error message in always mode ([#333](https://github.com/typescript-eslint/typescript-eslint/issues/333)) ([097262f](https://github.com/typescript-eslint/typescript-eslint/commit/097262f)) +- **eslint-plugin:** fix false positives for adjacent-overload-signatures regarding computed property names ([#340](https://github.com/typescript-eslint/typescript-eslint/issues/340)) ([f6e5118](https://github.com/typescript-eslint/typescript-eslint/commit/f6e5118)) +- **eslint-plugin:** fix incorrect rule name ([#357](https://github.com/typescript-eslint/typescript-eslint/issues/357)) ([0a5146b](https://github.com/typescript-eslint/typescript-eslint/commit/0a5146b)) + +### Features + +- **eslint-plugin:** Add unified-signature rule ([#178](https://github.com/typescript-eslint/typescript-eslint/issues/178)) ([6ffaa0b](https://github.com/typescript-eslint/typescript-eslint/commit/6ffaa0b)) + ## [1.4.2](https://github.com/typescript-eslint/typescript-eslint/compare/v1.4.1...v1.4.2) (2019-02-25) **Note:** Version bump only for package @typescript-eslint/eslint-plugin diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 10268b00635..22754a977c4 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -105,52 +105,55 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e -**Key**: :heavy_check_mark: = recommended, :wrench: = fixable +**Key**: :heavy_check_mark: = recommended, :wrench: = fixable, :thought_balloon: = requires type information -| Name | Description | :heavy_check_mark: | :wrench: | -| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | -| [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used (`ban-ts-ignore` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | -| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names (`class-name` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | -| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | -| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation (`indent` from TSLint) | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | :heavy_check_mark: | | +| Name | Description | :heavy_check_mark: | :wrench: | :thought_balloon: | +| --------------------------------------------------------------------------------------------------------- | --------------------------------------------------------------------------------------------------------------------------------------------------- | ------------------ | -------- | ----------------- | +| [`@typescript-eslint/adjacent-overload-signatures`](./docs/rules/adjacent-overload-signatures.md) | Require that member overloads be consecutive (`adjacent-overload-signatures` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/array-type`](./docs/rules/array-type.md) | Requires using either `T[]` or `Array` for arrays (`array-type` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/ban-types`](./docs/rules/ban-types.md) | Enforces that types will not to be used (`ban-types` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/ban-ts-ignore`](./docs/rules/ban-ts-ignore.md) | Bans “// @ts-ignore” comments from being used (`ban-ts-ignore` from TSLint) | | | | +| [`@typescript-eslint/camelcase`](./docs/rules/camelcase.md) | Enforce camelCase naming convention | :heavy_check_mark: | | | +| [`@typescript-eslint/class-name-casing`](./docs/rules/class-name-casing.md) | Require PascalCased class and interface names (`class-name` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/explicit-function-return-type`](./docs/rules/explicit-function-return-type.md) | Require explicit return types on functions and class methods | :heavy_check_mark: | | | +| [`@typescript-eslint/explicit-member-accessibility`](./docs/rules/explicit-member-accessibility.md) | Require explicit accessibility modifiers on class properties and methods (`member-access` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/generic-type-naming`](./docs/rules/generic-type-naming.md) | Enforces naming of generic type variables | | | | +| [`@typescript-eslint/indent`](./docs/rules/indent.md) | Enforce consistent indentation (`indent` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/interface-name-prefix`](./docs/rules/interface-name-prefix.md) | Require that interface names be prefixed with `I` (`interface-name` from TSLint) | :heavy_check_mark: | | | | [`@typescript-eslint/member-delimiter-style`](./docs/rules/member-delimiter-style.md) | Require a specific member delimiter style for interfaces and type literals | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | -| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | -| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions (`no-angle-bracket-type-assertion` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | | -| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop (`no-for-in-array` from TSLint) | | | -| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` (`no-require-imports` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` (`no-this-assignment` from TSLint) | | | -| [`@typescript-eslint/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments (`no-reference` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases (`interface-over-type-literal` from TSLint) | | | -| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary (`no-unnecessary-qualifier` from TSLint) | | :wrench: | -| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression (`no-unnecessary-type-assertion` from TSLint) | | :wrench: | -| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables (`no-unused-variable` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | -| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | -| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures (`callable-types` from TSLint) | | :wrench: | -| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | -| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | :heavy_check_mark: | | -| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | | -| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | +| [`@typescript-eslint/member-naming`](./docs/rules/member-naming.md) | Enforces naming conventions for class members by visibility. | | | | +| [`@typescript-eslint/member-ordering`](./docs/rules/member-ordering.md) | Require a consistent member declaration order (`member-ordering` from TSLint) | | | | +| [`@typescript-eslint/no-angle-bracket-type-assertion`](./docs/rules/no-angle-bracket-type-assertion.md) | Enforces the use of `as Type` assertions instead of `` assertions (`no-angle-bracket-type-assertion` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-array-constructor`](./docs/rules/no-array-constructor.md) | Disallow generic `Array` constructors | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/no-empty-interface`](./docs/rules/no-empty-interface.md) | Disallow the declaration of empty interfaces (`no-empty-interface` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-explicit-any`](./docs/rules/no-explicit-any.md) | Disallow usage of the `any` type (`no-any` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-extraneous-class`](./docs/rules/no-extraneous-class.md) | Forbids the use of classes as namespaces (`no-unnecessary-class` from TSLint) | | | | +| [`@typescript-eslint/no-for-in-array`](./docs/rules/no-for-in-array.md) | Disallow iterating over an array with a for-in loop (`no-for-in-array` from TSLint) | | | :thought_balloon: | +| [`@typescript-eslint/no-inferrable-types`](./docs/rules/no-inferrable-types.md) | Disallows explicit type declarations for variables or parameters initialized to a number, string, or boolean. (`no-inferrable-types` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/no-misused-new`](./docs/rules/no-misused-new.md) | Enforce valid definition of `new` and `constructor`. (`no-misused-new` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-namespace`](./docs/rules/no-namespace.md) | Disallow the use of custom TypeScript modules and namespaces (`no-namespace` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator (`no-non-null-assertion` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression (`no-object-literal-type-assertion` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors. (`no-parameter-properties` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` (`no-require-imports` from TSLint) | | | | +| [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` (`no-this-assignment` from TSLint) | | | | +| [`@typescript-eslint/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments (`no-reference` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-type-alias`](./docs/rules/no-type-alias.md) | Disallow the use of type aliases (`interface-over-type-literal` from TSLint) | | | | +| [`@typescript-eslint/no-unnecessary-qualifier`](./docs/rules/no-unnecessary-qualifier.md) | Warns when a namespace qualifier is unnecessary (`no-unnecessary-qualifier` from TSLint) | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/no-unnecessary-type-assertion`](./docs/rules/no-unnecessary-type-assertion.md) | Warns if a type assertion does not change the type of an expression (`no-unnecessary-type-assertion` from TSLint) | | :wrench: | :thought_balloon: | +| [`@typescript-eslint/no-unused-vars`](./docs/rules/no-unused-vars.md) | Disallow unused variables (`no-unused-variable` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/no-use-before-define`](./docs/rules/no-use-before-define.md) | Disallow the use of variables before they are defined | :heavy_check_mark: | | | +| [`@typescript-eslint/no-useless-constructor`](./docs/rules/no-useless-constructor.md) | Disallow unnecessary constructors | | | | +| [`@typescript-eslint/no-var-requires`](./docs/rules/no-var-requires.md) | Disallows the use of require statements except in import statements (`no-var-requires` from TSLint) | :heavy_check_mark: | | | +| [`@typescript-eslint/prefer-for-of`](./docs/rules/prefer-for-of.md) | Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated. | | | | +| [`@typescript-eslint/prefer-function-type`](./docs/rules/prefer-function-type.md) | Use function types instead of interfaces with call signatures (`callable-types` from TSLint) | | :wrench: | | +| [`@typescript-eslint/prefer-interface`](./docs/rules/prefer-interface.md) | Prefer an interface declaration over a type literal (type T = { ... }) (`interface-over-type-literal` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/prefer-namespace-keyword`](./docs/rules/prefer-namespace-keyword.md) | Require the use of the `namespace` keyword instead of the `module` keyword to declare custom TypeScript modules. (`no-internal-module` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/promise-function-async`](./docs/rules/promise-function-async.md) | Requires any function or method that returns a Promise to be marked async. (`promise-function-async` from TSLint) | | | :thought_balloon: | +| [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string. (`restrict-plus-operands` from TSLint) | | | :thought_balloon: | +| [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations (`typedef-whitespace` from TSLint) | :heavy_check_mark: | :wrench: | | +| [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope. (`no-unbound-method` from TSLint) | :heavy_check_mark: | | :thought_balloon: | +| [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one. (`unified-signatures` from TSLint) | | | | diff --git a/packages/eslint-plugin/ROADMAP.md b/packages/eslint-plugin/ROADMAP.md index e10dc03292a..6df8aa7fe31 100644 --- a/packages/eslint-plugin/ROADMAP.md +++ b/packages/eslint-plugin/ROADMAP.md @@ -30,11 +30,11 @@ | [`no-unnecessary-type-assertion`] | ✅ | [`@typescript-eslint/no-unnecessary-type-assertion`] | | [`no-var-requires`] | ✅ | [`@typescript-eslint/no-var-requires`] | | [`only-arrow-functions`] | 🔌 | [`prefer-arrow/prefer-arrow-functions`] | -| [`prefer-for-of`] | 🛑 | N/A | +| [`prefer-for-of`] | ✅ | [`@typescript-eslint/prefer-for-of`] | | [`promise-function-async`] | ✅ | [`@typescript-eslint/promise-function-async`] | | [`typedef`] | 🛑 | N/A | | [`typedef-whitespace`] | ✅ | [`@typescript-eslint/type-annotation-spacing`] | -| [`unified-signatures`] | 🛑 | N/A | +| [`unified-signatures`] | ✅ | [`@typescript-eslint/unified-signatures`] | ### Functionality @@ -77,7 +77,7 @@ | [`no-submodule-imports`] | 🌓 | [`import/no-internal-modules`] (slightly different) | | [`no-switch-case-fall-through`] | 🌟 | [`no-fallthrough`][no-fallthrough] | | [`no-this-assignment`] | ✅ | [`@typescript-eslint/no-this-alias`] | -| [`no-unbound-method`] | 🛑 | N/A | +| [`no-unbound-method`] | ✅ | [`@typescript-eslint/unbound-method`] | | [`no-unnecessary-class`] | ✅ | [`@typescript-eslint/no-extraneous-class`] | | [`no-unsafe-any`] | 🛑 | N/A | | [`no-unsafe-finally`] | 🌟 | [`no-unsafe-finally`][no-unsafe-finally] | @@ -586,9 +586,11 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/no-namespace`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-namespace.md [`@typescript-eslint/no-non-null-assertion`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-non-null-assertion.md [`@typescript-eslint/no-triple-slash-reference`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-triple-slash-reference.md +[`@typescript-eslint/unbound-method`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unbound-method.md [`@typescript-eslint/no-unnecessary-type-assertion`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-unnecessary-type-assertion.md [`@typescript-eslint/no-var-requires`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-var-requires.md [`@typescript-eslint/type-annotation-spacing`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/type-annotation-spacing.md +[`@typescript-eslint/unified-signatures`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/unified-signatures.md [`@typescript-eslint/no-misused-new`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-misused-new.md [`@typescript-eslint/no-object-literal-type-assertion`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-object-literal-type-assertion.md [`@typescript-eslint/no-this-alias`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-this-alias.md @@ -604,6 +606,7 @@ Relevant plugins: [`chai-expect-keywords`](https://github.com/gavinaiken/eslint- [`@typescript-eslint/no-angle-bracket-type-assertion`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-angle-bracket-type-assertion.md [`@typescript-eslint/no-parameter-properties`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/no-parameter-properties.md [`@typescript-eslint/member-delimiter-style`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/member-delimiter-style.md +[`@typescript-eslint/prefer-for-of`]: https://github.com/typescript-eslint/typescript-eslint/blob/master/packages/eslint-plugin/docs/rules/prefer-for-of.md [`@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 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 fb8ad9a7ffc..b6f37038aba 100644 --- a/packages/eslint-plugin/docs/rules/explicit-function-return-type.md +++ b/packages/eslint-plugin/docs/rules/explicit-function-return-type.md @@ -62,9 +62,11 @@ class Test { The rule accepts an options object with the following properties: - `allowExpressions` if true, only functions which are part of a declaration will be checked +- `allowTypedFunctionExpressions` if true, type annotations are also allowed on the variable + of a function expression rather than on the function directly. -By default, `allowExpressions: false` is used, meaning all declarations and -expressions _must_ have a return type. +By default, `allowExpressions: false` and `allowTypedFunctionExpressions: false` are used, +meaning all declarations and expressions _must_ have a return type. ### allowExpressions @@ -84,6 +86,20 @@ node.addEventListener('click', function() {}); const foo = arr.map(i => i * i); ``` +### allowTypedFunctionExpressions + +Examples of additional **correct** code for this rule with `{ allowTypedFunctionExpressions: true }`: + +```ts +type FuncType = () => string; + +let arrowFn: FuncType = () => 'test'; + +let funcExpr: FuncType = function() { + return 'test'; +}; +``` + ## When Not To Use It If you don't wish to prevent calling code from using function return values in unexpected ways, then diff --git a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md index ac3e8b1d7fc..a672b766c6c 100644 --- a/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md +++ b/packages/eslint-plugin/docs/rules/explicit-member-accessibility.md @@ -10,28 +10,247 @@ be easier to use. This rule aims to make code more readable and explicit about who can use which properties. -The following patterns are considered warnings: +## Options + +```ts +type AccessibilityLevel = + | 'explicit' // require an accessor (including public) + | 'no-public' // don't require public + | 'off'; // don't check + +interface Config { + accessibility?: AccessibilityLevel; + overrides?: { + accessors?: AccessibilityLevel; + constructors?: AccessibilityLevel; + methods?: AccessibilityLevel; + properties?: AccessibilityLevel; + parameterProperties?: AccessibilityLevel; + }; +} +``` + +Default config: + +```JSON +{ "accessibility": "explicit" } +``` + +This rule in it's default state requires no configuration and will enforce that every class member has an accessibility modifier. If you would like to allow for some implicit public members then you have the following options: +A possible configuration could be: + +```ts +{ + accessibility: 'explicit', + overrides { + accessors: 'explicit', + constructors: 'no-public', + methods: 'explicit', + properties: 'off', + parameterProperties: 'explicit' + } +} +``` + +The following patterns are considered incorrect code if no options are provided: + +```ts +class Animal { + constructor(name) { + // No accessibility modifier + this.animalName = name; + } + animalName: string; // No accessibility modifier + get name(): string { + // No accessibility modifier + return this.animalName; + } + set name(value: string) { + // No accessibility modifier + this.animalName = value; + } + walk() { + // method + } +} +``` + +The following patterns are considered correct with the default options `{ accessibility: 'explicit' }`: + +```ts +class Animal { + public constructor(public breed, animalName) { + // Parameter property and constructor + this.animalName = name; + } + private animalName: string; // Property + get name(): string { + // get accessor + return this.animalName; + } + set name(value: string) { + // set accessor + this.animalName = value; + } + public walk() { + // method + } +} +``` + +The following patterns are considered incorrect with the accessibility set to **no-public** `[{ accessibility: 'no-public' }]`: + +```ts +class Animal { + public constructor(public breed, animalName) { + // Parameter property and constructor + this.animalName = name; + } + public animalName: string; // Property + public get name(): string { + // get accessor + return this.animalName; + } + public set name(value: string) { + // set accessor + this.animalName = value; + } + public walk() { + // method + } +} +``` + +The following patterns are considered correct with the accessibility set to **no-public** `[{ accessibility: 'no-public' }]`: + +```ts +class Animal { + constructor(protected breed, animalName) { + // Parameter property and constructor + this.name = name; + } + private animalName: string; // Property + get name(): string { + // get accessor + return this.animalName; + } + private set name(value: string) { + // set accessor + this.animalName = value; + } + protected walk() { + // method + } +} +``` + +### Overrides + +There are three ways in which an override can be used. + +- To disallow the use of public on a given member. +- To enforce explicit member accessibility when the root has allowed implicit public accessibility +- To disable any checks on given member type + +#### Disallow the use of public on a given member + +e.g. `[ { overrides: { constructor: 'no-public' } } ]` + +The following patterns are considered incorrect with the example override + +```ts +class Animal { + public constructor(protected animalName) {} + public get name() { + return this.animalName; + } +} +``` + +The following patterns are considered correct with the example override + +```ts +class Animal { + constructor(protected animalName) {} + public get name() { + return this.animalName; + } +} +``` + +#### Require explicit accessibility for a given member + +e.g. `[ { accessibility: 'no-public', overrides: { properties: 'explicit' } } ]` + +The following patterns are considered incorrect with the example override + +```ts +class Animal { + constructor(protected animalName) {} + get name() { + return this.animalName; + } + protected set name(value: string) { + this.animalName = value; + } + legs: number; + private hasFleas: boolean; +} +``` + +The following patterns are considered correct with the example override + +```ts +class Animal { + constructor(protected animalName) {} + get name() { + return this.animalName; + } + protected set name(value: string) { + this.animalName = value; + } + public legs: number; + private hasFleas: boolean; +} +``` + +#### Disable any checks on given member type + +e.g. `[{ overrides: { accessors : 'off' } } ]` + +As no checks on the overridden member type are performed all permutations of visibility are permitted for that member type + +The follow pattern is considered incorrect for the given configuration ```ts class Animal { - name: string; // No accessibility modifier - getName(): string {} // No accessibility modifier + constructor(protected animalName) {} + public get name() { + return this.animalName; + } + get legs() { + return this.legCount; + } } ``` -The following patterns are not warnings: +The following patterns are considered correct with the example override ```ts class Animal { - private name: string; // explicit accessibility modifier - public getName(): string {} // explicit accessibility modifier + public constructor(protected animalName) {} + public get name() { + return this.animalName; + } + get legs() { + return this.legCount; + } } ``` ## When Not To Use It -If you think defaulting to public is a good default, then you will not need -this rule. +If you think defaulting to public is a good default, then you should consider using the `no-public` setting. If you want to mix implicit and explicit public members then disable this rule. ## Further Reading diff --git a/packages/eslint-plugin/docs/rules/member-naming.md b/packages/eslint-plugin/docs/rules/member-naming.md index 04c9e882c10..0d66a9f466c 100644 --- a/packages/eslint-plugin/docs/rules/member-naming.md +++ b/packages/eslint-plugin/docs/rules/member-naming.md @@ -4,7 +4,9 @@ It can be helpful to enforce naming conventions for `private` (and sometimes `pr ## Rule Details -This rule allows you to enforce conventions for class property names by their visibility. By default, it enforces nothing. +This rule allows you to enforce conventions for class property and method names by their visibility. By default, it enforces nothing. + +> Note: constructors are explicitly ignored regardless of the the regular expression options provided ## Options diff --git a/packages/eslint-plugin/docs/rules/member-ordering.md b/packages/eslint-plugin/docs/rules/member-ordering.md index a0b8f898356..50c4a7eacd3 100644 --- a/packages/eslint-plugin/docs/rules/member-ordering.md +++ b/packages/eslint-plugin/docs/rules/member-ordering.md @@ -5,591 +5,638 @@ expressions easier to read, navigate and edit. ## Rule Details -This rule aims to standardise the way interfaces, type literals, classes and class expressions are structured. +This rule aims to standardize the way class declarations, class expressions, interfaces and type literals are structured. -## Options +It allows to group members by their type (e.g. `public-static-field`, `protected-static-field`, `private-static-field`, `public-instance-field`, ...). By default, their order is the same inside `classes`, `classExpressions`, `interfaces` and `typeLiterals` (note: not all member types apply to `interfaces` and `typeLiterals`). It is possible to define the order for any of those individually or to change the default order for all of them by setting the `default` option. -This rule, in its default state, does not require any argument, in which case the following order is enforced: - -- `public-static-field` -- `protected-static-field` -- `private-static-field` -- `public-instance-field` -- `protected-instance-field` -- `private-instance-field` -- `public-field` (ignores scope) -- `protected-field` (ignores scope) -- `private-field` (ignores scope) -- `static-field` (ignores accessibility) -- `instance-field` (ignores accessibility) -- `field` (ignores scope and/or accessibility) -- `constructor` (ignores scope and/or accessibility) -- `public-static-method` -- `protected-static-method` -- `private-static-method` -- `public-instance-method` -- `protected-instance-method` -- `private-instance-method` -- `public-method` (ignores scope) -- `protected-method` (ignores scope) -- `private-method` (ignores scope) -- `static-method` (ignores accessibility) -- `instance-method` (ignores accessibility) -- `method` (ignores scope and/or accessibility) - -The rule can also take one or more of the following options: - -- `default`, use this to change the default order (used when no specific configuration has been provided). -- `classes`, use this to change the order in classes. -- `classExpressions`, use this to change the order in class expressions. -- `interfaces`, use this to change the order in interfaces. -- `typeLiterals`, use this to change the order in type literals. - -### default - -Disable using `never` or use one of the following values to specify an order: - -- Fields: - `public-static-field` - `protected-static-field` - `private-static-field` - `public-instance-field` - `protected-instance-field` - `private-instance-field` - `public-field` (= public-_-field) - `protected-field` (= protected-_-field) - `private-field` (= private-_-field) - `static-field` (= _-static-field) - `instance-field` (= \*-instance-field) - `field` (= all) - -- Constructors: - `public-constructor` - `protected-constructor` - `private-constructor` - `constructor` (= \*-constructor) - -- Methods: - `public-static-method` - `protected-static-method` - `private-static-method` - `public-instance-method` - `protected-instance-method` - `private-instance-method` - `public-method` (= public-_-method) - `protected-method` (= protected-_-method) - `private-method` (= private-_-method) - `static-method` (= _-static-method) - `instance-method` (= \*-instance-method) - `method` (= all) - -Examples of **incorrect** code for the `{ "default": [...] }` option: +## Options ```ts -// { "default": ["method", "constructor", "field"] } +{ + default?: Array | never + classes?: Array | never + classExpressions?: Array | never + + interfaces?: ['field' | 'method' | 'constructor'] | never + typeLiterals?: ['field' | 'method' | 'constructor'] | never +} +``` + +See below for the possible definitions of `MemberType`. + +### Member types (granular form) + +There are multiple ways to specify the member types. The most explicit and granular form is the following: + +```json5 +[ + // Fields + 'public-static-field', + 'protected-static-field', + 'private-static-field', + 'public-instance-field', + 'protected-instance-field', + 'private-instance-field', + + // Constructors + 'public-constructor', + 'protected-constructor', + 'private-constructor', + + // Methods + 'public-static-method', + 'protected-static-method', + 'private-static-method', + 'public-instance-method', + 'protected-instance-method', + 'private-instance-method', +] +``` + +Note: If you only specify some of the possible types, the non-specified ones can have any particular order. This means that they can be placed before, within or after the specified types and the linter won't complain about it. + +### Member group types (with accessibility, ignoring scope) + +It is also possible to group member types by their accessibility (`static`, `instance`), ignoring their scope. + +```json5 +[ + // Fields + 'public-field', // = ['public-static-field', 'public-instance-field']) + 'protected-field', // = ['protected-static-field', 'protected-instance-field']) + 'private-field', // = ['private-static-field', 'private-instance-field']) + + // Constructors + // Only the accessibility of constructors is configurable. See below. + + // Methods + 'public-method', // = ['public-static-method', 'public-instance-method']) + 'protected-method', // = ['protected-static-method', 'protected-instance-method']) + 'private-method', // = ['private-static-method', 'private-instance-method']) +] +``` + +### Member group types (with scope, ignoring accessibility) + +Another option is to group the member types by their scope (`public`, `protected`, `private`), ignoring their accessibility. + +```json5 +[ + // Fields + 'static-field', // = ['public-static-field', 'protected-static-field', 'private-static-field']) + 'instance-field', // = ['public-instance-field', 'protected-instance-field', 'private-instance-field']) + + // Constructors + 'constructor', // = ['public-constructor', 'protected-constructor', 'private-constructor']) + + // Methods + 'static-method', // = ['public-static-method', 'protected-static-method', 'private-static-method']) + 'instance-method', // = ['public-instance-method', 'protected-instance-method', 'private-instance-method'] +] +``` + +### Member group types (with scope and accessibility) + +The third grouping option is to ignore both scope and accessibility. + +```json5 +[ + // Fields + 'field', // = ['public-static-field', 'protected-static-field', 'private-static-field', 'public-instance-field', 'protected-instance-field', 'private-instance-field']) + + // Constructors + // Only the accessibility of constructors is configurable. See above. + + // Methods + 'method', // = ['public-static-method', 'protected-static-method', 'private-static-method', 'public-instance-method', 'protected-instance-method', 'private-instance-method']) +] +``` + +### Default configuration + +The default configuration looks as follows: + +```json +{ + "default": [ + "public-static-field", + "protected-static-field", + "private-static-field", + + "public-instance-field", + "protected-instance-field", + "private-instance-field", + + "public-field", + "protected-field", + "private-field", + + "static-field", + "instance-field", + + "field", + + "constructor", + + "public-static-method", + "protected-static-method", + "private-static-method", + + "public-instance-method", + "protected-instance-method", + "private-instance-method", + + "public-method", + "protected-method", + "private-method", + + "static-method", + "instance-method", + + "method" + ] +} +``` + +Note: The default configuration contains member group types which contain other member types (see above). This is intentional to provide better error messages. + +## Examples + +### Custom `default` configuration + +Note: The `default` options are overwritten in these examples. +#### Configuration: `{ "default": ["method", "constructor", "field"] }` + +##### Incorrect examples + +```ts interface Foo { - // -> field - B: string; + B: string; // -> field - // -> constructor - new (); + new (); // -> constructor - // -> method - A(): void; + A(): void; // -> method } +``` + +Note: Wrong order. +```ts type Foo = { - // -> field - B: string; + B: string; // -> field // no constructor - // -> method - A(): void; + A(): void; // -> method }; +``` + +Note: Not all specified member types have to exist. +```ts class Foo { - // -> * field - private C: string; - public D: string; - protected static E: string; + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field - // -> constructor - constructor() {} + constructor() {} // -> constructor - // -> * method - public static A(): void {} - public B(): void {} + public static A(): void {} // -> method + public B(): void {} // -> method } +``` +Note: Accessibility or scope are ignored with this ignored. + +```ts const Foo = class { - // -> * field - private C: string; - public D: string; + private C: string; // -> field + public D: string; // -> field - // -> constructor - constructor() {} + constructor() {} // -> constructor - // -> * method - public static A(): void {} - public B(): void {} + public static A(): void {} // -> method + public B(): void {} // -> method - // * field - protected static E: string; + protected static E: string; // -> field }; +``` -// { "default": ["public-instance-method", "public-static-field"] } +Note: Not all members have to be grouped to find rule violations. -// does not apply for interfaces/type literals (accessibility and scope are not part of interfaces/type literals) +##### Correct examples -class Foo { - // private instance field - private C: string; +```ts +interface Foo { + A(): void; // -> method - // public instance field - public D: string; + new (); // -> constructor - // -> public static field - public static E: string; + B: string; // -> field +} +``` - // constructor - constructor() {} +```ts +type Foo = { + A(): void; // -> method - // public static method - public static A(): void {} + // no constructor - // -> public instance method - public B(): void {} -} + B: string; // -> field +}; +``` -const Foo = class { - // private instance field - private C: string; +```ts +class Foo { + public static A(): void {} // -> method + public B(): void {} // -> method - // -> public static field - public static E: string; + constructor() {} // -> constructor - // public instance field - public D: string; + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field +} +``` - // constructor - constructor() {} +```ts +const Foo = class { + public static A(): void {} // -> method + public B(): void {} // -> method - // public static method - public static A(): void {} + constructor() {} // -> constructor - // -> public instance method - public B(): void {} + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field }; ``` -Examples of **correct** code for the `{ "default": [...] }` option: +#### Configuration: `{ "default": ["public-instance-method", "public-static-field"] }` + +Note: This configuration does not apply to interfaces/type literals as accessibility and scope are not part of interfaces/type literals. + +##### Incorrect examples ```ts -// { "default": ["method", "constructor", "field"] } +class Foo { + private C: string; // (irrelevant) -interface Foo { - // -> method - A() : void; + public D: string; // (irrelevant) - // -> constructor - new(); + public static E: string; // -> public static field - // -> field - B: string; -} + constructor() {} // (irrelevant) -type Foo = { - // -> method - A() : void; + public static A(): void {} // (irrelevant) - // -> field - B: string; + public B(): void {} // -> public instance method } +``` -class Foo { - // -> * method - public static A(): void {} - public B(): void {} +Note: Public instance methods should come first before public static fields. Everything else can be placed anywhere. + +```ts +const Foo = class { + private C: string; // (irrelevant) - // -> constructor - constructor() {} + public static E: string; // -> public static field - // -> * field - private C: string - public D: string - protected static E: string -} + public D: string; // (irrelevant) -const Foo = class { - // -> * method - public static A(): void {} - public B(): void {} + constructor() {} // (irrelevant) - // -> constructor - constructor() {} + public static A(): void {} // (irrelevant) - // -> * field - private C: string - public D: string - protected static E: string -} + public B(): void {} // -> public instance method +}; +``` -// { "default": ["public-instance-method", "public-static-field"] } +Note: Public instance methods should come first before public static fields. Everything else can be placed anywhere. -// does not apply for interfaces/type literals (accessibility and scope are not part of interfaces/type literals) +##### Correct examples +```ts class Foo { - // -> public instance method - public B(): void {} + public B(): void {} // -> public instance method - // private instance field - private C: string + private C: string; // (irrelevant) - // public instance field - public D: string + public D: string; // (irrelevant) - // -> public static field - public static E: string + public static E: string; // -> public static field - // constructor - constructor() {} + constructor() {} // (irrelevant) - // public static method - public static A(): void {} + public static A(): void {} // (irrelevant) } +``` +```ts const Foo = class { - // -> public instance method - public B(): void {} + public B(): void {} // -> public instance method - // private instance field - private C: string + private C: string; // (irrelevant) - // public instance field - public D: string + public D: string; // (irrelevant) - // constructor - constructor() {} + constructor() {} // (irrelevant) - // public static method - public static A(): void {} + public static A(): void {} // (irrelevant) - // -> protected static field - protected static: string -} + public static E: string; // -> public static field +}; +``` -// { "default": ["public-static-field", "static-field", "instance-field"] } +#### Configuration: `{ "default": ["public-static-field", "static-field", "instance-field"] }` -// does not apply for interfaces/type literals (accessibility and scope are not part of interfaces/type literals) +Note: This configuration does not apply to interfaces/type literals as accessibility and scope are not part of interfaces/type literals. +##### Incorrect examples + +```ts class Foo { - // -> public static field - public static A: string; + private E: string; // -> instance field - // -> * static field - private static B: string; - protected statis C:string; - private static D: string; + private static B: string; // -> static field + protected static C: string; // -> static field + private static D: string; // -> static field - // -> * instance field - private E: string; + public static A: string; // -> public static field } +``` + +Note: Public static fields should come first, followed by static fields and instance fields. +```ts const foo = class { - // * method - public T(): void {} + public T(): void {} // (irrelevant) - // -> public static field - public static A: string; + private static B: string; // -> static field - // constructor - constructor(){} + constructor() {} // (irrelevant) - // -> * static field - private static B: string; - protected statis C:string; - private static D: string; + private E: string; // -> instance field - // -> * instance field - private E: string; -} + protected static C: string; // -> static field + private static D: string; // -> static field + + public static A: string; // -> public static field +}; ``` -### classes +Issue: Public static fields should come first, followed by static fields and instance fields. + +##### Correct examples + +```ts +class Foo { + public static A: string; // -> public static field -Disable using `never` or use one of the valid values (see default) to specify an order. + private static B: string; // -> static field + protected static C: string; // -> static field + private static D: string; // -> static field -Examples of **incorrect** code for the `{ "classes": [...] }` option: + private E: string; // -> instance field +} +``` ```ts -// { "classes": ["method", "constructor", "field"] } +const foo = class { + public static A: string; // -> public static field -// does not apply for interfaces/type literals/class expressions. + constructor() {} // -> constructor -class Foo { - // -> field - private C: string; - public D: string; - protected static E: string; + private static B: string; // -> static field + protected static C: string; // -> static field + private static D: string; // -> static field - // -> constructor - constructor() {} + private E: string; // -> instance field - // -> method - public static A(): void {} - public B(): void {} -} + public T(): void {} // -> method +}; +``` -// { "classes": ["public-instance-method", "public-static-field"] } +### Custom `classes` configuration -// does not apply for interfaces/type literals/class expressions. +Note: If this is not set, the `default` will automatically be applied to classes as well. If a `classes` configuration is provided, only this configuration will be used for `classes` (i.e. nothing will be merged with `default`). -class Foo { - // private instance field - private C: string; +Note: The configuration for `classes` does not apply to class expressions (use `classExpressions` for them). - // public instance field - public D: string; +#### Configuration: `{ "classes": ["method", "constructor", "field"] }` - // -> public static field - public static E: string; +##### Incorrect example - // constructor - constructor() {} +```ts +class Foo { + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field - // public static method - public static A(): void {} + constructor() {} // -> constructor - // -> public instance method - public B(): void {} + public static A(): void {} // -> method + public B(): void {} // -> method } ``` -Examples of **correct** code for `{ "classes": [...] }` option: +##### Correct example ```ts -// { "classes": ["method", "constructor", "field"] } - -// does not apply for interfaces/type literals/class expressions. - class Foo { - // -> * method - public static A(): void {} - public B(): void {} + public static A(): void {} // -> method + public B(): void {} // -> method - // -> constructor - constructor() {} + constructor() {} // -> constructor - // -> * field - private C: string; - public D: string; - protected static E: string; + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field } +``` -// { "classes": ["public-instance-method", "public-static-field"] } +#### Configuration: `{ "classes": ["public-instance-method", "public-static-field"] }` -// does not apply for interfaces/type literals/class expressions. +##### Incorrect example +```ts class Foo { - // private instance field - private C: string; + private C: string; // (irrelevant) - // public instance field - public D: string; + public D: string; // (irrelevant) - // -> public static field - public static E: string; + public static E: string; // -> public static field - // constructor - constructor() {} + constructor() {} // (irrelevant) - // public static method - public static A(): void {} + public static A(): void {} // (irrelevant) - // -> public instance method - public B(): void {} + public B(): void {} // -> public instance method } ``` -### classExpressions +##### Correct example -Disable using `never` or use one of the valid values (see default) to specify an order. - -Examples of **incorrect** code for the `{ "classExpressions": [...] }` option: +Examples of **correct** code for `{ "classes": [...] }` option: ```ts -// { "classExpressions": ["method", "constructor", "field"] } +class Foo { + private C: string; // (irrelevant) -// does not apply for interfaces/type literals/class expressions. + public D: string; // (irrelevant) -const foo = class { - // -> field - private C: string; - public D: string; - protected static E: string; + public static E: string; // -> public static field - // -> constructor - constructor() {} + constructor() {} // (irrelevant) - // -> method - public static A(): void {} - public B(): void {} -}; + public static A(): void {} // (irrelevant) -// { "classExpressions": ["public-instance-method", "public-static-field"] } + public B(): void {} // -> public instance method +} +``` -// does not apply for interfaces/type literals/class expressions. +### Custom `classExpressions` configuration -const foo = class { - // private instance field - private C: string; +Note: If this is not set, the `default` will automatically be applied to classes expressions as well. If a `classExpressions` configuration is provided, only this configuration will be used for `classExpressions` (i.e. nothing will be merged with `default`). - // public instance field - public D: string; +Note: The configuration for `classExpressions` does not apply to classes (use `classes` for them). - // -> public static field - public static E: string; +#### Configuration: `{ "classExpressions": ["method", "constructor", "field"] }` - // constructor - constructor() {} +##### Incorrect example + +```ts +const foo = class { + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field - // public static method - public static A(): void {} + constructor() {} // -> constructor - // -> public instance method - public B(): void {} + public static A(): void {} // -> method + public B(): void {} // -> method }; ``` -Examples of **correct** code for `{ "classExpressions": [...] }` option: +##### Correct example ```ts -// { "classExpressions": ["method", "constructor", "field"] } +const foo = class { + public static A(): void {} // -> method + public B(): void {} // -> method + + constructor() {} // -> constructor + + private C: string; // -> field + public D: string; // -> field + protected static E: string; // -> field +}; +``` + +#### Configuration: `{ "classExpressions": ["public-instance-method", "public-static-field"] }` -// does not apply for interfaces/type literals/class expressions. +##### Incorrect example +```ts const foo = class { - // -> * method - public static A(): void {} - public B(): void {} + private C: string; // (irrelevant) - // -> constructor - constructor() {} + public D: string; // (irrelevant) - // -> * field - private C: string; - public D: string; - protected static E: string; -}; + public static E: string; // -> public static field + + constructor() {} // (irrelevant) -// { "classExpressions": ["public-instance-method", "public-static-field"] } + public static A(): void {} // (irrelevant) -// does not apply for interfaces/type literals/class expressions. + public B(): void {} // -> public instance method +}; +``` + +##### Correct example +```ts const foo = class { - // private instance field - private C: string; + private C: string; // (irrelevant) - // public instance field - public D: string; + public D: string; // (irrelevant) - // -> public static field - public static E: string; + public B(): void {} // -> public instance method - // constructor - constructor() {} + public static E: string; // -> public static field - // public static method - public static A(): void {} + constructor() {} // (irrelevant) - // -> public instance method - public B(): void {} + public static A(): void {} // (irrelevant) }; ``` -### interfaces +### Custom `interfaces` configuration -Disable using `never` or use one of the following values to specify an order: -`field` -`constructor` -`method` +Note: If this is not set, the `default` will automatically be applied to classes expressions as well. If a `interfaces` configuration is provided, only this configuration will be used for `interfaces` (i.e. nothing will be merged with `default`). -Examples of **incorrect** code for the `{ "interfaces": [...] }` option: +Note: The configuration for `interfaces` only allows a limited set of member types: `field`, `constructor` and `method`. -```ts -// { "interfaces": ["method", "constructor", "field"] } +Note: The configuration for `interfaces` does not apply to type literals (use `typeLiterals` for them). -// does not apply for classes/class expressions/type literals +#### Configuration: `{ "interfaces": ["method", "constructor", "field"] }` +##### Incorrect example + +```ts interface Foo { - // -> field - B: string; + B: string; // -> field - // -> constructor - new (); + new (); // -> constructor - // -> method - A(): void; + A(): void; // -> method } ``` -Examples of **correct** code for the `{ "interfaces": [...] }` option: +##### Correct example ```ts -// { "interfaces": ["method", "constructor", "field"] } - -// does not apply for classes/class expressions/type literals - interface Foo { - // -> method - A(): void; + A(): void; // -> method - // -> constructor - new (); + new (); // -> constructor - // -> field - B: string; + B: string; // -> field } ``` -### typeLiterals +### Custom `typeLiterals` configuration -Disable using `never` or use one of the valid values (see interfaces) to specify an order. +Note: If this is not set, the `default` will automatically be applied to classes expressions as well. If a `typeLiterals` configuration is provided, only this configuration will be used for `typeLiterals` (i.e. nothing will be merged with `default`). -Examples of **incorrect** code for the `{ "typeLiterals": [...] }` option: +Note: The configuration for `typeLiterals` only allows a limited set of member types: `field`, `constructor` and `method`. -```ts -// { "typeLiterals": ["method", "constructor", "field"] } +Note: The configuration for `typeLiterals` does not apply to type literals (use `interfaces` for them). -// does not apply for classes/class expressions/interfaces +#### Configuration: `{ "typeLiterals": ["method", "constructor", "field"] }` +##### Incorrect example + +```ts type Foo = { - // -> field - B: string; + B: string; // -> field + + A(): void; // -> method - // -> method - A(): void; + new (); // -> constructor }; ``` -Examples of **correct** code for the `{ "typeLiterals": [...] }` option: +##### Correct example ```ts -// { "typeLiterals": ["method", "constructor", "field"] } - -// does not apply for classes/class expressions/interfaces - type Foo = { - // -> method - A(): void; + A(): void; // -> method - // -> constructor - new (); + new (); // -> constructor - // -> field - B: string; + B: string; // -> field }; ``` diff --git a/packages/eslint-plugin/docs/rules/prefer-for-of.md b/packages/eslint-plugin/docs/rules/prefer-for-of.md new file mode 100644 index 00000000000..a937709f7eb --- /dev/null +++ b/packages/eslint-plugin/docs/rules/prefer-for-of.md @@ -0,0 +1,41 @@ +# Use for-of loops instead of standard for loops over arrays (prefer-for-of) + +This rule recommends a for-of loop when the loop index is only used to read from an array that is being iterated. + +## Rule Details + +For cases where the index is only used to read from the array being iterated, a for-of loop is easier to read and write. + +Examples of **incorrect** code for this rule: + +```js +for (let i = 0; i < arr.length; i++) { + console.log(arr[i]); +} +``` + +Examples of **correct** code for this rule: + +```js +for (const x of arr) { + console.log(x); +} + +for (let i = 0; i < arr.length; i++) { + // i is used to write to arr, so for-of could not be used. + arr[i] = 0; +} + +for (let i = 0; i < arr.length; i++) { + // i is used independent of arr, so for-of could not be used. + console.log(i, arr[i]); +} +``` + +## When Not To Use It + +If you transpile for browsers that do not support for-of loops, you may wish to use traditional for loops that produce more compact code. + +## Related to + +- TSLint: ['prefer-for-of'](https://palantir.github.io/tslint/rules/prefer-for-of/) diff --git a/packages/eslint-plugin/docs/rules/unbound-method.md b/packages/eslint-plugin/docs/rules/unbound-method.md new file mode 100644 index 00000000000..d0268dadeec --- /dev/null +++ b/packages/eslint-plugin/docs/rules/unbound-method.md @@ -0,0 +1,92 @@ +# Enforces unbound methods are called with their expected scope (unbound-method) + +Warns when a method is used outside of a method call. + +Class functions don't preserve the class scope when passed as standalone variables. + +## Rule Details + +Examples of **incorrect** code for this rule + +```ts +class MyClass { + public log(): void { + console.log(this); + } +} + +const instance = new MyClass(); + +// This logs the global scope (`window`/`global`), not the class instance +const myLog = instance.log; +myLog(); + +// This log might later be called with an incorrect scope +const { log } = instance; +``` + +Examples of **correct** code for this rule + +```ts +class MyClass { + public logUnbound(): void { + console.log(this); + } + + public logBound = () => console.log(this); +} + +const instance = new MyClass(); + +// logBound will always be bound with the correct scope +const { logBound } = instance; +logBound(); + +// .bind and lambdas will also add a correct scope +const dotBindLog = instance.log.bind(instance); +const innerLog = () => instance.log(); +``` + +## Options + +The rule accepts an options object with the following property: + +- `ignoreStatic` to not check whether `static` methods are correctly bound + +### `ignoreStatic` + +Examples of **correct** code for this rule with `{ ignoreStatic: true }`: + +```ts +class OtherClass { + static log() { + console.log(OtherClass); + } +} + +// With `ignoreStatic`, statics are assumed to not rely on a particular scope +const { log } = OtherClass; + +log(); +``` + +### Example + +```json +{ + "@typescript-eslint/unbound-method": [ + "error", + { + "ignoreStatic": true + } + ] +} +``` + +## When Not To Use It + +If your code intentionally waits to bind methods after use, such as by passing a `scope: this` along with the method, you can disable this rule. + +## Related To + +- TSLint: [no-unbound-method](https://palantir.github.io/tslint/rules/no-unbound-method/) diff --git a/packages/eslint-plugin/docs/rules/unified-signatures.md b/packages/eslint-plugin/docs/rules/unified-signatures.md new file mode 100644 index 00000000000..7fe46beceaf --- /dev/null +++ b/packages/eslint-plugin/docs/rules/unified-signatures.md @@ -0,0 +1,33 @@ +# Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter. (unified-signatures) + +Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter. + +## Rule Details + +This rule aims to keep the source code as maintanable as posible by reducing the amount of overloads. + +Examples of **incorrect** code for this rule: + +```ts +function f(x: number): void; +function f(x: string): void; +``` + +```ts +f(): void; +f(...x: number[]): void; +``` + +Examples of **correct** code for this rule: + +```ts +function f(x: number | string): void; +``` + +```ts +function f(x?: ...number[]): void; +``` + +## Related to + +- TSLint: [`unified-signatures`](https://palantir.github.io/tslint/rules/unified-signatures/) diff --git a/packages/eslint-plugin/package.json b/packages/eslint-plugin/package.json index 8dc2059db69..6a292d723bd 100644 --- a/packages/eslint-plugin/package.json +++ b/packages/eslint-plugin/package.json @@ -1,6 +1,6 @@ { "name": "@typescript-eslint/eslint-plugin", - "version": "1.4.2", + "version": "1.6.0", "description": "TypeScript plugin for ESLint", "keywords": [ "eslint", @@ -35,8 +35,8 @@ "typecheck": "tsc --noEmit" }, "dependencies": { - "@typescript-eslint/parser": "1.4.2", - "@typescript-eslint/typescript-estree": "1.4.2", + "@typescript-eslint/parser": "1.6.0", + "@typescript-eslint/typescript-estree": "1.6.0", "eslint-utils": "^1.3.1", "regexpp": "^2.0.1", "requireindex": "^1.2.0", diff --git a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts index 7007b1c3bdf..8e864c9c4a2 100644 --- a/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts +++ b/packages/eslint-plugin/src/rules/adjacent-overload-signatures.ts @@ -26,6 +26,8 @@ export default util.createRule({ }, defaultOptions: [], create(context) { + const sourceCode = context.getSourceCode(); + /** * Gets the name of the member being processed. * @param member the member being processed. @@ -57,7 +59,7 @@ export default util.createRule({ case AST_NODE_TYPES.TSConstructSignatureDeclaration: return 'new'; case AST_NODE_TYPES.MethodDefinition: - return util.getNameFromPropertyName(member.key); + return util.getNameFromClassMember(member, sourceCode); } return null; diff --git a/packages/eslint-plugin/src/rules/camelcase.ts b/packages/eslint-plugin/src/rules/camelcase.ts index 009ba186aa1..ec7ba9ea4fe 100644 --- a/packages/eslint-plugin/src/rules/camelcase.ts +++ b/packages/eslint-plugin/src/rules/camelcase.ts @@ -6,7 +6,7 @@ type Options = util.InferOptionsTypeFromRule; type MessageIds = util.InferMessageIdsTypeFromRule; export default util.createRule({ - name: 'ban-types', + name: 'camelcase', meta: { type: 'suggestion', docs: { 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 e4f5dd46a3c..0d9f9304d2a 100644 --- a/packages/eslint-plugin/src/rules/explicit-function-return-type.ts +++ b/packages/eslint-plugin/src/rules/explicit-function-return-type.ts @@ -4,6 +4,7 @@ import * as util from '../util'; type Options = [ { allowExpressions?: boolean; + allowTypedFunctionExpressions?: boolean; } ]; type MessageIds = 'missingReturnType'; @@ -28,6 +29,9 @@ export default util.createRule({ allowExpressions: { type: 'boolean', }, + allowTypedFunctionExpressions: { + type: 'boolean', + }, }, additionalProperties: false, }, @@ -35,28 +39,42 @@ export default util.createRule({ }, defaultOptions: [ { - allowExpressions: true, + allowExpressions: false, + allowTypedFunctionExpressions: false, }, ], create(context, [options]) { /** - * Checks if the parent of a function expression is a constructor. - * @param parent The parent of a function expression node + * Checks if a node is a constructor. + * @param node The node to check + */ + function isConstructor(node: TSESTree.Node): boolean { + return ( + node.type === AST_NODE_TYPES.MethodDefinition && + node.kind === 'constructor' + ); + } + + /** + * Checks if a node is a setter. + * @param parent The node to check */ - function isConstructor(parent: TSESTree.Node): boolean { + function isSetter(node: TSESTree.Node): boolean { return ( - parent.type === AST_NODE_TYPES.MethodDefinition && - parent.kind === 'constructor' + node.type === AST_NODE_TYPES.MethodDefinition && node.kind === 'set' ); } /** - * Checks if the parent of a function expression is a setter. - * @param parent The parent of a function expression node + * Checks if a node is a variable declarator with a type annotation. + * @param node The node to check */ - function isSetter(parent: TSESTree.Node): boolean { + function isVariableDeclaratorWithTypeAnnotation( + node: TSESTree.Node, + ): boolean { return ( - parent.type === AST_NODE_TYPES.MethodDefinition && parent.kind === 'set' + node.type === AST_NODE_TYPES.VariableDeclarator && + !!node.id.typeAnnotation ); } @@ -70,6 +88,16 @@ export default util.createRule({ | TSESTree.FunctionDeclaration | TSESTree.FunctionExpression, ): void { + if ( + options.allowExpressions && + node.type !== AST_NODE_TYPES.FunctionDeclaration && + node.parent && + node.parent.type !== AST_NODE_TYPES.VariableDeclarator && + node.parent.type !== AST_NODE_TYPES.MethodDefinition + ) { + return; + } + if ( !node.returnType && node.parent && @@ -95,10 +123,9 @@ export default util.createRule({ | TSESTree.FunctionExpression, ): void { if ( - options.allowExpressions && + options.allowTypedFunctionExpressions && node.parent && - node.parent.type !== AST_NODE_TYPES.VariableDeclarator && - node.parent.type !== AST_NODE_TYPES.MethodDefinition + isVariableDeclaratorWithTypeAnnotation(node.parent) ) { return; } diff --git a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts index afae022c262..be79d684a69 100644 --- a/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts +++ b/packages/eslint-plugin/src/rules/explicit-member-accessibility.ts @@ -1,7 +1,29 @@ -import { TSESTree } from '@typescript-eslint/typescript-estree'; +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; import * as util from '../util'; -export default util.createRule({ +type AccessibilityLevel = + | 'explicit' // require an accessor (including public) + | 'no-public' // don't require public + | 'off'; // don't check + +interface Config { + accessibility?: AccessibilityLevel; + overrides?: { + accessors?: AccessibilityLevel; + constructors?: AccessibilityLevel; + methods?: AccessibilityLevel; + properties?: AccessibilityLevel; + parameterProperties?: AccessibilityLevel; + }; +} + +type Options = [Config]; + +type MessageIds = 'unwantedPublicAccessibility' | 'missingAccessibility'; + +const accessibilityLevel = { enum: ['explicit', 'no-public', 'off'] }; + +export default util.createRule({ name: 'explicit-member-accessibility', meta: { type: 'problem', @@ -15,11 +37,60 @@ export default util.createRule({ messages: { missingAccessibility: 'Missing accessibility modifier on {{type}} {{name}}.', + unwantedPublicAccessibility: + 'Public accessibility modifier on {{type}} {{name}}.', }, - schema: [], + schema: [ + { + type: 'object', + properties: { + accessibility: accessibilityLevel, + overrides: { + type: 'object', + properties: { + accessors: accessibilityLevel, + constructors: accessibilityLevel, + methods: accessibilityLevel, + properties: accessibilityLevel, + parameterProperties: accessibilityLevel, + }, + additionalProperties: false, + }, + }, + additionalProperties: false, + }, + ], }, - defaultOptions: [], - create(context) { + defaultOptions: [{ accessibility: 'explicit' }], + create(context, [option]) { + const sourceCode = context.getSourceCode(); + const baseCheck: AccessibilityLevel = option.accessibility || 'explicit'; + const overrides = option.overrides || {}; + const ctorCheck = overrides.constructors || baseCheck; + const accessorCheck = overrides.accessors || baseCheck; + const methodCheck = overrides.methods || baseCheck; + const propCheck = overrides.properties || baseCheck; + const paramPropCheck = overrides.parameterProperties || baseCheck; + + /** + * Generates the report for rule violations + */ + function reportIssue( + messageId: MessageIds, + nodeType: string, + node: TSESTree.Node, + nodeName: string, + ) { + context.report({ + node: node, + messageId: messageId, + data: { + type: nodeType, + name: nodeName, + }, + }); + } + /** * Checks if a method declaration has an accessibility modifier. * @param methodDefinition The node representing a MethodDefinition. @@ -27,18 +98,49 @@ export default util.createRule({ function checkMethodAccessibilityModifier( methodDefinition: TSESTree.MethodDefinition, ): void { - if ( - !methodDefinition.accessibility && - util.isTypeScriptFile(context.getFilename()) - ) { - context.report({ - node: methodDefinition, - messageId: 'missingAccessibility', - data: { - type: 'method definition', - name: util.getNameFromPropertyName(methodDefinition.key), - }, - }); + let nodeType = 'method definition'; + let check = baseCheck; + switch (methodDefinition.kind) { + case 'method': + check = methodCheck; + break; + case 'constructor': + check = ctorCheck; + break; + case 'get': + case 'set': + check = accessorCheck; + nodeType = `${methodDefinition.kind} property accessor`; + break; + } + if (check === 'off') { + return; + } + + if (util.isTypeScriptFile(context.getFilename())) { + // const methodName = util.getNameFromPropertyName(methodDefinition.key); + const methodName = util.getNameFromClassMember( + methodDefinition, + sourceCode, + ); + if ( + check === 'no-public' && + methodDefinition.accessibility === 'public' + ) { + reportIssue( + 'unwantedPublicAccessibility', + nodeType, + methodDefinition, + methodName, + ); + } else if (check === 'explicit' && !methodDefinition.accessibility) { + reportIssue( + 'missingAccessibility', + nodeType, + methodDefinition, + methodName, + ); + } } } @@ -49,22 +151,62 @@ export default util.createRule({ function checkPropertyAccessibilityModifier( classProperty: TSESTree.ClassProperty, ): void { - if ( - !classProperty.accessibility && - util.isTypeScriptFile(context.getFilename()) - ) { - context.report({ - node: classProperty, - messageId: 'missingAccessibility', - data: { - type: 'class property', - name: util.getNameFromPropertyName(classProperty.key), - }, - }); + const nodeType = 'class property'; + + if (util.isTypeScriptFile(context.getFilename())) { + const propertyName = util.getNameFromPropertyName(classProperty.key); + if ( + propCheck === 'no-public' && + classProperty.accessibility === 'public' + ) { + reportIssue( + 'unwantedPublicAccessibility', + nodeType, + classProperty, + propertyName, + ); + } else if (propCheck === 'explicit' && !classProperty.accessibility) { + reportIssue( + 'missingAccessibility', + nodeType, + classProperty, + propertyName, + ); + } + } + } + + /** + * Checks that the parameter property has the desired accessibility modifiers set. + * @param {TSESTree.TSParameterProperty} node The node representing a Parameter Property + */ + function checkParameterPropertyAccessibilityModifier( + node: TSESTree.TSParameterProperty, + ) { + const nodeType = 'parameter property'; + if (util.isTypeScriptFile(context.getFilename())) { + // HAS to be an identifier or assignment or TSC will throw + if ( + node.parameter.type !== AST_NODE_TYPES.Identifier && + node.parameter.type !== AST_NODE_TYPES.AssignmentPattern + ) { + return; + } + + const nodeName = + node.parameter.type === AST_NODE_TYPES.Identifier + ? node.parameter.name + : // has to be an Identifier or TSC will throw an error + (node.parameter.left as TSESTree.Identifier).name; + + if (paramPropCheck === 'no-public' && node.accessibility === 'public') { + reportIssue('unwantedPublicAccessibility', nodeType, node, nodeName); + } } } return { + TSParameterProperty: checkParameterPropertyAccessibilityModifier, ClassProperty: checkPropertyAccessibilityModifier, MethodDefinition: checkMethodAccessibilityModifier, }; diff --git a/packages/eslint-plugin/src/rules/indent.ts b/packages/eslint-plugin/src/rules/indent.ts index a3c6f5971d5..85187a7046b 100644 --- a/packages/eslint-plugin/src/rules/indent.ts +++ b/packages/eslint-plugin/src/rules/indent.ts @@ -74,6 +74,7 @@ const KNOWN_NODES = new Set([ AST_NODE_TYPES.TSTypeOperator, AST_NODE_TYPES.TSTypeParameter, AST_NODE_TYPES.TSTypeParameterDeclaration, + AST_NODE_TYPES.TSTypeParameterInstantiation, AST_NODE_TYPES.TSTypeReference, AST_NODE_TYPES.TSUnionType, ]); diff --git a/packages/eslint-plugin/src/rules/member-naming.ts b/packages/eslint-plugin/src/rules/member-naming.ts index 23c14c8b39c..1e6fcdd226e 100644 --- a/packages/eslint-plugin/src/rules/member-naming.ts +++ b/packages/eslint-plugin/src/rules/member-naming.ts @@ -51,6 +51,8 @@ export default util.createRule({ }, defaultOptions: [{}], create(context, [config]) { + const sourceCode = context.getSourceCode(); + const conventions = (Object.keys(config) as Modifiers[]).reduce< Config >((acc, accessibility) => { @@ -69,10 +71,13 @@ export default util.createRule({ function validateName( node: TSESTree.MethodDefinition | TSESTree.ClassProperty, ): void { - const name = util.getNameFromPropertyName(node.key); + const name = util.getNameFromClassMember(node, sourceCode); const accessibility: Modifiers = node.accessibility || 'public'; const convention = conventions[accessibility]; + const method = node as TSESTree.MethodDefinition; + if (method.kind === 'constructor') return; + if (!convention || convention.test(name)) return; context.report({ diff --git a/packages/eslint-plugin/src/rules/member-ordering.ts b/packages/eslint-plugin/src/rules/member-ordering.ts index dff4752bab4..6811cf5a2e4 100644 --- a/packages/eslint-plugin/src/rules/member-ordering.ts +++ b/packages/eslint-plugin/src/rules/member-ordering.ts @@ -13,23 +13,26 @@ type Options = [ } ]; -const schemaOptions = ['field', 'method', 'constructor'].reduce( - (options, type) => { - options.push(type); +const allMemberTypes = ['field', 'method', 'constructor'].reduce( + (all, type) => { + all.push(type); ['public', 'protected', 'private'].forEach(accessibility => { - options.push(`${accessibility}-${type}`); + all.push(`${accessibility}-${type}`); // e.g. `public-field` + if (type !== 'constructor') { + // There is no `static-constructor` or `instance-constructor ['static', 'instance'].forEach(scope => { - if (options.indexOf(`${scope}-${type}`) === -1) { - options.push(`${scope}-${type}`); + if (all.indexOf(`${scope}-${type}`) === -1) { + all.push(`${scope}-${type}`); } - options.push(`${accessibility}-${scope}-${type}`); + + all.push(`${accessibility}-${scope}-${type}`); }); } }); - return options; + return all; }, [], ); @@ -60,7 +63,7 @@ export default util.createRule({ { type: 'array', items: { - enum: schemaOptions, + enum: allMemberTypes, }, }, ], @@ -73,7 +76,7 @@ export default util.createRule({ { type: 'array', items: { - enum: schemaOptions, + enum: allMemberTypes, }, }, ], @@ -86,7 +89,7 @@ export default util.createRule({ { type: 'array', items: { - enum: schemaOptions, + enum: allMemberTypes, }, }, ], @@ -164,6 +167,8 @@ export default util.createRule({ }, ], create(context, [options]) { + const sourceCode = context.getSourceCode(); + const functionExpressions = [ AST_NODE_TYPES.FunctionExpression, AST_NODE_TYPES.ArrowFunctionExpression, @@ -213,7 +218,7 @@ export default util.createRule({ case AST_NODE_TYPES.MethodDefinition: return node.kind === 'constructor' ? 'constructor' - : util.getNameFromPropertyName(node.key); + : util.getNameFromClassMember(node, sourceCode); case AST_NODE_TYPES.TSConstructSignatureDeclaration: return 'new'; default: @@ -228,12 +233,14 @@ export default util.createRule({ * - If there is no order for accessibility-scope-type, then strip out the accessibility. * - If there is no order for scope-type, then strip out the scope. * - If there is no order for type, then return -1 - * @param names the valid names to be validated. + * @param memberTypes the valid names to be validated. * @param order the current order to be validated. + * + * @return Index of the matching member type in the order configuration. */ - function getRankOrder(names: string[], order: string[]): number { + function getRankOrder(memberTypes: string[], order: string[]): number { let rank = -1; - const stack = names.slice(); + const stack = memberTypes.slice(); // Get a copy of the member types while (stack.length > 0 && rank === -1) { rank = order.indexOf(stack.shift()!); @@ -246,7 +253,7 @@ export default util.createRule({ * Gets the rank of the node given the order. * @param node the node to be evaluated. * @param order the current order to be validated. - * @param supportsModifiers a flag indicating whether the type supports modifiers or not. + * @param supportsModifiers a flag indicating whether the type supports modifiers (scope or accessibility) or not. */ function getRank( node: TSESTree.ClassElement | TSESTree.TypeElement, @@ -265,19 +272,21 @@ export default util.createRule({ ? node.accessibility : 'public'; - const names = []; + const memberTypes = []; if (supportsModifiers) { if (type !== 'constructor') { - names.push(`${accessibility}-${scope}-${type}`); - names.push(`${scope}-${type}`); + // Constructors have no scope + memberTypes.push(`${accessibility}-${scope}-${type}`); + memberTypes.push(`${scope}-${type}`); } - names.push(`${accessibility}-${type}`); + + memberTypes.push(`${accessibility}-${type}`); } - names.push(type); + memberTypes.push(type); - return getRankOrder(names, order); + return getRankOrder(memberTypes, order); } /** @@ -316,12 +325,13 @@ export default util.createRule({ } /** - * Validates each member rank. - * @param members the members to be validated. - * @param order the current order to be validated. - * @param supportsModifiers a flag indicating whether the type supports modifiers or not. + * Validates if all members are correctly sorted. + * + * @param members Members to be validated. + * @param order Current order to be validated. + * @param supportsModifiers A flag indicating whether the type supports modifiers (scope or accessibility) or not. */ - function validateMembers( + function validateMembersOrder( members: (TSESTree.ClassElement | TSESTree.TypeElement)[], order: OrderConfig, supportsModifiers: boolean, @@ -329,6 +339,7 @@ export default util.createRule({ if (members && order !== 'never') { const previousRanks: number[] = []; + // Find first member which isn't correctly sorted members.forEach(member => { const rank = getRank(member, order, supportsModifiers); @@ -352,28 +363,28 @@ export default util.createRule({ return { ClassDeclaration(node) { - validateMembers( + validateMembersOrder( node.body.body, options.classes || options.default!, true, ); }, ClassExpression(node) { - validateMembers( + validateMembersOrder( node.body.body, options.classExpressions || options.default!, true, ); }, TSInterfaceDeclaration(node) { - validateMembers( + validateMembersOrder( node.body.body, options.interfaces || options.default!, false, ); }, TSTypeLiteral(node) { - validateMembers( + validateMembersOrder( node.members, options.typeLiterals || options.default!, false, diff --git a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts index a5e6e6788c9..d8362013321 100644 --- a/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts +++ b/packages/eslint-plugin/src/rules/no-object-literal-type-assertion.ts @@ -9,7 +9,7 @@ type Options = [ type MessageIds = 'unexpectedTypeAssertion'; export default util.createRule({ - name: 'no-object-literal-type-assertions', + name: 'no-object-literal-type-assertion', meta: { type: 'problem', docs: { @@ -50,6 +50,12 @@ export default util.createRule({ case AST_NODE_TYPES.TSAnyKeyword: case AST_NODE_TYPES.TSUnknownKeyword: return false; + case AST_NODE_TYPES.TSTypeReference: + // Ignore `as const` and `` (#166) + return ( + node.typeName.type === AST_NODE_TYPES.Identifier && + node.typeName.name !== 'const' + ); default: return true; } diff --git a/packages/eslint-plugin/src/rules/no-type-alias.ts b/packages/eslint-plugin/src/rules/no-type-alias.ts index c15795773a0..890e6b04aa5 100644 --- a/packages/eslint-plugin/src/rules/no-type-alias.ts +++ b/packages/eslint-plugin/src/rules/no-type-alias.ts @@ -108,6 +108,7 @@ export default util.createRule({ AST_NODE_TYPES.TSArrayType, AST_NODE_TYPES.TSTypeReference, AST_NODE_TYPES.TSLiteralType, + AST_NODE_TYPES.TSTypeQuery, ]; type CompositionType = TSESTree.TSUnionType | TSESTree.TSIntersectionType; diff --git a/packages/eslint-plugin/src/rules/prefer-for-of.ts b/packages/eslint-plugin/src/rules/prefer-for-of.ts new file mode 100644 index 00000000000..4b542cd9109 --- /dev/null +++ b/packages/eslint-plugin/src/rules/prefer-for-of.ts @@ -0,0 +1,217 @@ +import { AST_NODE_TYPES, TSESTree } from '@typescript-eslint/typescript-estree'; +import * as util from '../util'; +import { Scope } from 'ts-eslint'; + +export default util.createRule({ + name: 'prefer-for-of', + meta: { + type: 'suggestion', + docs: { + description: + 'Prefer a ‘for-of’ loop over a standard ‘for’ loop if the index is only used to access the array being iterated.', + category: 'Stylistic Issues', + recommended: false, + tslintName: 'prefer-for-of', + }, + messages: { + preferForOf: + 'Expected a `for-of` loop instead of a `for` loop with this simple iteration', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + function isSingleVariableDeclaration( + node: TSESTree.Node | null, + ): node is TSESTree.VariableDeclaration { + return ( + node !== null && + node.type === AST_NODE_TYPES.VariableDeclaration && + node.kind !== 'const' && + node.declarations.length === 1 + ); + } + + function isLiteral(node: TSESTree.Expression, value: number): boolean { + return node.type === AST_NODE_TYPES.Literal && node.value === value; + } + + function isZeroInitialized(node: TSESTree.VariableDeclarator): boolean { + return node.init !== null && isLiteral(node.init, 0); + } + + function isMatchingIdentifier( + node: TSESTree.Expression, + name: string, + ): boolean { + return node.type === AST_NODE_TYPES.Identifier && node.name === name; + } + + function isLessThanLengthExpression( + node: TSESTree.Node | null, + name: string, + ): TSESTree.Expression | null { + if ( + node !== null && + node.type === AST_NODE_TYPES.BinaryExpression && + node.operator === '<' && + isMatchingIdentifier(node.left, name) && + node.right.type === AST_NODE_TYPES.MemberExpression && + isMatchingIdentifier(node.right.property, 'length') + ) { + return node.right.object; + } + return null; + } + + function isIncrement(node: TSESTree.Node | null, name: string): boolean { + if (!node) { + return false; + } + switch (node.type) { + case AST_NODE_TYPES.UpdateExpression: + // x++ or ++x + return ( + node.operator === '++' && isMatchingIdentifier(node.argument, name) + ); + case AST_NODE_TYPES.AssignmentExpression: + if (isMatchingIdentifier(node.left, name)) { + if (node.operator === '+=') { + // x += 1 + return isLiteral(node.right, 1); + } else if (node.operator === '=') { + // x = x + 1 or x = 1 + x + const expr = node.right; + return ( + expr.type === AST_NODE_TYPES.BinaryExpression && + expr.operator === '+' && + ((isMatchingIdentifier(expr.left, name) && + isLiteral(expr.right, 1)) || + (isLiteral(expr.left, 1) && + isMatchingIdentifier(expr.right, name))) + ); + } + } + } + return false; + } + + function contains(outer: TSESTree.Node, inner: TSESTree.Node): boolean { + return ( + outer.range[0] <= inner.range[0] && outer.range[1] >= inner.range[1] + ); + } + + function isAssignee(node: TSESTree.Node): boolean { + const parent = node.parent; + if (!parent) { + return false; + } + + // a[i] = 1, a[i] += 1, etc. + if ( + parent.type === AST_NODE_TYPES.AssignmentExpression && + parent.left === node + ) { + return true; + } + + // delete a[i] + if ( + parent.type === AST_NODE_TYPES.UnaryExpression && + parent.operator === 'delete' && + parent.argument === node + ) { + return true; + } + + // a[i]++, --a[i], etc. + if ( + parent.type === AST_NODE_TYPES.UpdateExpression && + parent.argument === node + ) { + return true; + } + + // [a[i]] = [0] + if (parent.type === AST_NODE_TYPES.ArrayPattern) { + return true; + } + + // [...a[i]] = [0] + if (parent.type === AST_NODE_TYPES.RestElement) { + return true; + } + + // ({ foo: a[i] }) = { foo: 0 } + if ( + parent.type === AST_NODE_TYPES.Property && + parent.parent !== undefined && + parent.parent.type === AST_NODE_TYPES.ObjectExpression && + parent.value === node && + isAssignee(parent.parent) + ) { + return true; + } + + return false; + } + + function isIndexOnlyUsedWithArray( + body: TSESTree.Statement, + indexVar: Scope.Variable, + arrayExpression: TSESTree.Expression, + ): boolean { + const sourceCode = context.getSourceCode(); + const arrayText = sourceCode.getText(arrayExpression); + return indexVar.references.every(reference => { + const id = reference.identifier; + const node = id.parent; + return ( + !contains(body, id) || + (node !== undefined && + node.type === AST_NODE_TYPES.MemberExpression && + node.property === id && + sourceCode.getText(node.object) === arrayText && + !isAssignee(node)) + ); + }); + } + + return { + 'ForStatement:exit'(node: TSESTree.ForStatement) { + if (!isSingleVariableDeclaration(node.init)) { + return; + } + + const [declarator] = node.init.declarations; + if ( + !isZeroInitialized(declarator) || + declarator.id.type !== AST_NODE_TYPES.Identifier + ) { + return; + } + + const indexName = declarator.id.name; + const arrayExpression = isLessThanLengthExpression( + node.test, + indexName, + ); + if (!arrayExpression) { + return; + } + + const [indexVar] = context.getDeclaredVariables(node.init); + if ( + isIncrement(node.update, indexName) && + isIndexOnlyUsedWithArray(node.body, indexVar, arrayExpression) + ) { + context.report({ + node, + messageId: 'preferForOf', + }); + } + }, + }; + }, +}); diff --git a/packages/eslint-plugin/src/rules/unbound-method.ts b/packages/eslint-plugin/src/rules/unbound-method.ts new file mode 100644 index 00000000000..621e95ec7c5 --- /dev/null +++ b/packages/eslint-plugin/src/rules/unbound-method.ts @@ -0,0 +1,123 @@ +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; +import * as tsutils from 'tsutils'; +import * as ts from 'typescript'; + +import * as util from '../util'; + +//------------------------------------------------------------------------------ +// Rule Definition +//------------------------------------------------------------------------------ + +interface Config { + ignoreStatic: boolean; +} + +type Options = [Config]; + +type MessageIds = 'unbound'; + +export default util.createRule({ + name: 'unbound-method', + meta: { + docs: { + category: 'Best Practices', + description: + 'Enforces unbound methods are called with their expected scope.', + tslintName: 'no-unbound-method', + recommended: 'error', + }, + messages: { + unbound: + 'Avoid referencing unbound methods which may cause unintentional scoping of `this`.', + }, + schema: [ + { + type: 'object', + properties: { + ignoreStatic: { + type: 'boolean', + }, + }, + additionalProperties: false, + }, + ], + type: 'problem', + }, + defaultOptions: [ + { + ignoreStatic: false, + }, + ], + create(context, [{ ignoreStatic }]) { + const parserServices = util.getParserServices(context); + const checker = parserServices.program.getTypeChecker(); + + return { + [AST_NODE_TYPES.MemberExpression](node: TSESTree.MemberExpression) { + if (isSafeUse(node)) { + return; + } + + const originalNode = parserServices.esTreeNodeToTSNodeMap.get(node); + const symbol = checker.getSymbolAtLocation(originalNode); + + if (symbol && isDangerousMethod(symbol, ignoreStatic)) { + context.report({ + messageId: 'unbound', + node, + }); + } + }, + }; + }, +}); + +function isDangerousMethod(symbol: ts.Symbol, ignoreStatic: boolean) { + const { valueDeclaration } = symbol; + + switch (valueDeclaration.kind) { + case ts.SyntaxKind.MethodDeclaration: + case ts.SyntaxKind.MethodSignature: + return !( + ignoreStatic && + tsutils.hasModifier( + valueDeclaration.modifiers, + ts.SyntaxKind.StaticKeyword, + ) + ); + } + + return false; +} + +function isSafeUse(node: TSESTree.Node): boolean { + const parent = node.parent!; + + switch (parent.type) { + case AST_NODE_TYPES.IfStatement: + case AST_NODE_TYPES.ForStatement: + case AST_NODE_TYPES.MemberExpression: + case AST_NODE_TYPES.UpdateExpression: + case AST_NODE_TYPES.WhileStatement: + return true; + + case AST_NODE_TYPES.CallExpression: + return parent.callee === node; + + case AST_NODE_TYPES.ConditionalExpression: + return parent.test === node; + + case AST_NODE_TYPES.LogicalExpression: + return parent.operator !== '||'; + + case AST_NODE_TYPES.TaggedTemplateExpression: + return parent.tag === node; + + case AST_NODE_TYPES.TSNonNullExpression: + case AST_NODE_TYPES.TSAsExpression: + case AST_NODE_TYPES.TSTypeAssertion: + return isSafeUse(parent); + } + + return false; +} diff --git a/packages/eslint-plugin/src/rules/unified-signatures.ts b/packages/eslint-plugin/src/rules/unified-signatures.ts new file mode 100644 index 00000000000..1b54bd4217f --- /dev/null +++ b/packages/eslint-plugin/src/rules/unified-signatures.ts @@ -0,0 +1,576 @@ +import * as util from '../util'; +import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; + +interface Failure { + unify: Unify; + only2: boolean; +} + +type Unify = + | { + kind: 'single-parameter-difference'; + p0: TSESTree.Parameter; + p1: TSESTree.Parameter; + } + | { + kind: 'extra-parameter'; + extraParameter: TSESTree.Parameter; + otherSignature: SignatureDefinition; + }; + +/** + * Returns true if typeName is the name of an *outer* type parameter. + * In: `interface I { m(x: U): T }`, only `T` is an outer type parameter. + */ +type IsTypeParameter = (typeName: string) => boolean; + +type ScopeNode = + | TSESTree.Program + | TSESTree.TSModuleBlock + | TSESTree.TSInterfaceBody + | TSESTree.ClassBody + | TSESTree.TSTypeLiteral; + +type OverloadNode = MethodDefinition | SignatureDefinition; + +type SignatureDefinition = + | TSESTree.FunctionExpression + | TSESTree.TSCallSignatureDeclaration + | TSESTree.TSConstructSignatureDeclaration + | TSESTree.TSDeclareFunction + | TSESTree.TSEmptyBodyFunctionExpression + | TSESTree.TSMethodSignature; + +type MethodDefinition = + | TSESTree.MethodDefinition + | TSESTree.TSAbstractMethodDefinition; + +export default util.createRule({ + name: 'unified-signatures', + meta: { + docs: { + description: + 'Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter.', + category: 'Variables', + recommended: false, + tslintName: 'unified-signatures', + }, + type: 'suggestion', + messages: { + omittingRestParameter: '{{failureStringStart}} with a rest parameter.', + omittingSingleParameter: + '{{failureStringStart}} with an optional parameter.', + singleParameterDifference: + '{{failureStringStart}} taking `{{type1}} | {{type2}}`.', + }, + schema: [], + }, + defaultOptions: [], + create(context) { + const sourceCode = context.getSourceCode(); + + //---------------------------------------------------------------------- + // Helpers + //---------------------------------------------------------------------- + + function failureStringStart(otherLine?: number): string { + // For only 2 overloads we don't need to specify which is the other one. + const overloads = + otherLine === undefined + ? 'These overloads' + : `This overload and the one on line ${otherLine}`; + return `${overloads} can be combined into one signature`; + } + + function addFailures(failures: Failure[]): void { + for (const failure of failures) { + const { unify, only2 } = failure; + switch (unify.kind) { + case 'single-parameter-difference': { + const { p0, p1 } = unify; + const lineOfOtherOverload = only2 ? undefined : p0.loc.start.line; + + const typeAnnotation0 = isTSParameterProperty(p0) + ? p0.parameter.typeAnnotation + : p0.typeAnnotation; + const typeAnnotation1 = isTSParameterProperty(p1) + ? p1.parameter.typeAnnotation + : p1.typeAnnotation; + + context.report({ + loc: p1.loc, + messageId: 'singleParameterDifference', + data: { + failureStringStart: failureStringStart(lineOfOtherOverload), + type1: sourceCode.getText( + typeAnnotation0 && typeAnnotation0.typeAnnotation, + ), + type2: sourceCode.getText( + typeAnnotation1 && typeAnnotation1.typeAnnotation, + ), + }, + node: p1, + }); + break; + } + case 'extra-parameter': { + const { extraParameter, otherSignature } = unify; + const lineOfOtherOverload = only2 + ? undefined + : otherSignature.loc.start.line; + + context.report({ + loc: extraParameter.loc, + messageId: + extraParameter.type === AST_NODE_TYPES.RestElement + ? 'omittingRestParameter' + : 'omittingSingleParameter', + data: { + failureStringStart: failureStringStart(lineOfOtherOverload), + }, + node: extraParameter, + }); + } + } + } + } + + function checkOverloads( + signatures: ReadonlyArray, + typeParameters?: TSESTree.TSTypeParameterDeclaration, + ): Failure[] { + const result: Failure[] = []; + const isTypeParameter = getIsTypeParameter(typeParameters); + for (const overloads of signatures) { + if (overloads.length === 2) { + const signature0 = + (overloads[0] as MethodDefinition).value || overloads[0]; + const signature1 = + (overloads[1] as MethodDefinition).value || overloads[1]; + + const unify = compareSignatures( + signature0, + signature1, + isTypeParameter, + ); + if (unify !== undefined) { + result.push({ unify, only2: true }); + } + } else { + forEachPair(overloads, (a, b) => { + const signature0 = (a as MethodDefinition).value || a; + const signature1 = (b as MethodDefinition).value || b; + + const unify = compareSignatures( + signature0, + signature1, + isTypeParameter, + ); + if (unify !== undefined) { + result.push({ unify, only2: false }); + } + }); + } + } + return result; + } + + function compareSignatures( + a: SignatureDefinition, + b: SignatureDefinition, + isTypeParameter: IsTypeParameter, + ): Unify | undefined { + if (!signaturesCanBeUnified(a, b, isTypeParameter)) { + return undefined; + } + + return a.params.length === b.params.length + ? signaturesDifferBySingleParameter(a.params, b.params) + : signaturesDifferByOptionalOrRestParameter(a, b); + } + + function signaturesCanBeUnified( + a: SignatureDefinition, + b: SignatureDefinition, + isTypeParameter: IsTypeParameter, + ): boolean { + // Must return the same type. + + const aTypeParams = + a.typeParameters !== undefined ? a.typeParameters.params : undefined; + const bTypeParams = + b.typeParameters !== undefined ? b.typeParameters.params : undefined; + + return ( + typesAreEqual(a.returnType, b.returnType) && + // Must take the same type parameters. + // If one uses a type parameter (from outside) and the other doesn't, they shouldn't be joined. + util.arraysAreEqual(aTypeParams, bTypeParams, typeParametersAreEqual) && + signatureUsesTypeParameter(a, isTypeParameter) === + signatureUsesTypeParameter(b, isTypeParameter) + ); + } + + /** Detect `a(x: number, y: number, z: number)` and `a(x: number, y: string, z: number)`. */ + function signaturesDifferBySingleParameter( + types1: ReadonlyArray, + types2: ReadonlyArray, + ): Unify | undefined { + const index = getIndexOfFirstDifference( + types1, + types2, + parametersAreEqual, + ); + if (index === undefined) { + return undefined; + } + + // If remaining arrays are equal, the signatures differ by just one parameter type + if ( + !util.arraysAreEqual( + types1.slice(index + 1), + types2.slice(index + 1), + parametersAreEqual, + ) + ) { + return undefined; + } + + const a = types1[index]; + const b = types2[index]; + // Can unify `a?: string` and `b?: number`. Can't unify `...args: string[]` and `...args: number[]`. + // See https://github.com/Microsoft/TypeScript/issues/5077 + return parametersHaveEqualSigils(a, b) && + a.type !== AST_NODE_TYPES.RestElement + ? { kind: 'single-parameter-difference', p0: a, p1: b } + : undefined; + } + + /** + * Detect `a(): void` and `a(x: number): void`. + * Returns the parameter declaration (`x: number` in this example) that should be optional/rest, and overload it's a part of. + */ + function signaturesDifferByOptionalOrRestParameter( + a: SignatureDefinition, + b: SignatureDefinition, + ): Unify | undefined { + const sig1 = a.params; + const sig2 = b.params; + + const minLength = Math.min(sig1.length, sig2.length); + const longer = sig1.length < sig2.length ? sig2 : sig1; + const shorter = sig1.length < sig2.length ? sig1 : sig2; + const shorterSig = sig1.length < sig2.length ? a : b; + + // If one is has 2+ parameters more than the other, they must all be optional/rest. + // Differ by optional parameters: f() and f(x), f() and f(x, ?y, ...z) + // Not allowed: f() and f(x, y) + for (let i = minLength + 1; i < longer.length; i++) { + if (!parameterMayBeMissing(longer[i])) { + return undefined; + } + } + + for (let i = 0; i < minLength; i++) { + const sig1i = sig1[i]; + const sig2i = sig2[i]; + const typeAnnotation1 = isTSParameterProperty(sig1i) + ? sig1i.parameter.typeAnnotation + : sig1i.typeAnnotation; + const typeAnnotation2 = isTSParameterProperty(sig2i) + ? sig2i.parameter.typeAnnotation + : sig2i.typeAnnotation; + + if (!typesAreEqual(typeAnnotation1, typeAnnotation2)) { + return undefined; + } + } + + if ( + minLength > 0 && + shorter[minLength - 1].type === AST_NODE_TYPES.RestElement + ) { + return undefined; + } + + return { + extraParameter: longer[longer.length - 1], + kind: 'extra-parameter', + otherSignature: shorterSig, + }; + } + + /** Given type parameters, returns a function to test whether a type is one of those parameters. */ + function getIsTypeParameter( + typeParameters?: TSESTree.TSTypeParameterDeclaration, + ): IsTypeParameter { + if (typeParameters === undefined) { + return () => false; + } + + const set = new Set(); + for (const t of typeParameters.params) { + set.add(t.name.name); + } + return typeName => set.has(typeName); + } + + /** True if any of the outer type parameters are used in a signature. */ + function signatureUsesTypeParameter( + sig: SignatureDefinition, + isTypeParameter: IsTypeParameter, + ): boolean { + return sig.params.some((p: TSESTree.Parameter) => + typeContainsTypeParameter( + isTSParameterProperty(p) + ? p.parameter.typeAnnotation + : p.typeAnnotation, + ), + ); + + function typeContainsTypeParameter( + type?: TSESTree.TSTypeAnnotation | TSESTree.TypeNode, + ): boolean { + if (!type) { + return false; + } + + if (type.type === AST_NODE_TYPES.TSTypeReference) { + const typeName = type.typeName; + if (isIdentifier(typeName) && isTypeParameter(typeName.name)) { + return true; + } + } + + return typeContainsTypeParameter( + (type as TSESTree.TSTypeAnnotation).typeAnnotation || + (type as TSESTree.TSArrayType).elementType, + ); + } + } + + function isTSParameterProperty( + node: TSESTree.Node, + ): node is TSESTree.TSParameterProperty { + return ( + (node as TSESTree.TSParameterProperty).type === + AST_NODE_TYPES.TSParameterProperty + ); + } + + function parametersAreEqual( + a: TSESTree.Parameter, + b: TSESTree.Parameter, + ): boolean { + const typeAnnotationA = isTSParameterProperty(a) + ? a.parameter.typeAnnotation + : a.typeAnnotation; + const typeAnnotationB = isTSParameterProperty(b) + ? b.parameter.typeAnnotation + : b.typeAnnotation; + + return ( + parametersHaveEqualSigils(a, b) && + typesAreEqual(typeAnnotationA, typeAnnotationB) + ); + } + + /** True for optional/rest parameters. */ + function parameterMayBeMissing(p: TSESTree.Parameter): boolean | undefined { + const optional = isTSParameterProperty(p) + ? p.parameter.optional + : p.optional; + + return p.type === AST_NODE_TYPES.RestElement || optional; + } + + /** False if one is optional and the other isn't, or one is a rest parameter and the other isn't. */ + function parametersHaveEqualSigils( + a: TSESTree.Parameter, + b: TSESTree.Parameter, + ): boolean { + const optionalA = isTSParameterProperty(a) + ? a.parameter.optional + : a.optional; + const optionalB = isTSParameterProperty(b) + ? b.parameter.optional + : b.optional; + + return ( + (a.type === AST_NODE_TYPES.RestElement) === + (b.type === AST_NODE_TYPES.RestElement) && + (optionalA !== undefined) === (optionalB !== undefined) + ); + } + + function typeParametersAreEqual( + a: TSESTree.TSTypeParameter, + b: TSESTree.TSTypeParameter, + ): boolean { + return ( + a.name.name === b.name.name && + constraintsAreEqual(a.constraint, b.constraint) + ); + } + + function typesAreEqual( + a: TSESTree.TSTypeAnnotation | undefined, + b: TSESTree.TSTypeAnnotation | undefined, + ): boolean { + return ( + a === b || + (a !== undefined && + b !== undefined && + a.typeAnnotation.type === b.typeAnnotation.type) + ); + } + + function constraintsAreEqual( + a: TSESTree.TypeNode | undefined, + b: TSESTree.TypeNode | undefined, + ): boolean { + return ( + a === b || (a !== undefined && b !== undefined && a.type === b.type) + ); + } + + /* Returns the first index where `a` and `b` differ. */ + function getIndexOfFirstDifference( + a: ReadonlyArray, + b: ReadonlyArray, + equal: util.Equal, + ): number | undefined { + for (let i = 0; i < a.length && i < b.length; i++) { + if (!equal(a[i], b[i])) { + return i; + } + } + return undefined; + } + + /** Calls `action` for every pair of values in `values`. */ + function forEachPair( + values: ReadonlyArray, + action: (a: T, b: T) => void, + ): void { + for (let i = 0; i < values.length; i++) { + for (let j = i + 1; j < values.length; j++) { + action(values[i], values[j]); + } + } + } + + interface Scope { + overloads: Map; + parent?: ScopeNode; + typeParameters?: TSESTree.TSTypeParameterDeclaration; + } + + const scopes: Scope[] = []; + let currentScope: Scope = { + overloads: new Map(), + }; + + function createScope( + parent: ScopeNode, + typeParameters?: TSESTree.TSTypeParameterDeclaration, + ) { + currentScope && scopes.push(currentScope); + currentScope = { + overloads: new Map(), + parent, + typeParameters, + }; + } + + function checkScope() { + const failures = checkOverloads( + Array.from(currentScope.overloads.values()), + currentScope.typeParameters, + ); + addFailures(failures); + currentScope = scopes.pop()!; + } + + function addOverload(signature: OverloadNode, key?: string) { + key = key || getOverloadKey(signature); + if (currentScope && signature.parent === currentScope.parent && key) { + const overloads = currentScope.overloads.get(key); + if (overloads !== undefined) { + overloads.push(signature); + } else { + currentScope.overloads.set(key, [signature]); + } + } + } + + //---------------------------------------------------------------------- + // Public + //---------------------------------------------------------------------- + + return { + Program: createScope, + TSModuleBlock: createScope, + TSInterfaceDeclaration(node) { + createScope(node.body, node.typeParameters); + }, + ClassDeclaration(node) { + createScope(node.body, node.typeParameters); + }, + TSTypeLiteral: createScope, + // collect overloads + TSDeclareFunction(node) { + if (node.id && !node.body) { + addOverload(node, node.id.name); + } + }, + TSCallSignatureDeclaration: addOverload, + TSConstructSignatureDeclaration: addOverload, + TSMethodSignature: addOverload, + TSAbstractMethodDefinition(node) { + if (!node.value.body) { + addOverload(node); + } + }, + MethodDefinition(node) { + if (!node.value.body) { + addOverload(node); + } + }, + // validate scopes + 'Program:exit': checkScope, + 'TSModuleBlock:exit': checkScope, + 'TSInterfaceDeclaration:exit': checkScope, + 'ClassDeclaration:exit': checkScope, + 'TSTypeLiteral:exit': checkScope, + }; + }, +}); + +function getOverloadKey(node: OverloadNode): string | undefined { + const info = getOverloadInfo(node); + + return ( + ((node as MethodDefinition).computed ? '0' : '1') + + ((node as MethodDefinition).static ? '0' : '1') + + info + ); +} + +function getOverloadInfo(node: OverloadNode): string { + switch (node.type) { + case AST_NODE_TYPES.TSConstructSignatureDeclaration: + return 'constructor'; + case AST_NODE_TYPES.TSCallSignatureDeclaration: + return '()'; + default: { + const { key } = node as MethodDefinition; + + return isIdentifier(key) ? key.name : (key as TSESTree.Literal).raw; + } + } +} + +function isIdentifier(node: TSESTree.Node): node is TSESTree.Identifier { + return node.type === AST_NODE_TYPES.Identifier; +} diff --git a/packages/eslint-plugin/src/util/createRule.ts b/packages/eslint-plugin/src/util/createRule.ts index 97b0f18c51e..ac61c39cb55 100644 --- a/packages/eslint-plugin/src/util/createRule.ts +++ b/packages/eslint-plugin/src/util/createRule.ts @@ -26,7 +26,7 @@ type CreateRuleMeta = { // This function will get much easier to call when this is merged https://github.com/Microsoft/TypeScript/pull/26349 // TODO - when the above rule lands; add type checking for the context.report `data` property export function createRule< - TOptions extends Readonly, + TOptions extends any[], TMessageIds extends string, TRuleListener extends RuleListener = RuleListener >({ diff --git a/packages/eslint-plugin/src/util/misc.ts b/packages/eslint-plugin/src/util/misc.ts index dbaaebc3f7a..4e3d34937f9 100644 --- a/packages/eslint-plugin/src/util/misc.ts +++ b/packages/eslint-plugin/src/util/misc.ts @@ -4,6 +4,7 @@ import { TSESTree, AST_NODE_TYPES } from '@typescript-eslint/typescript-estree'; import RuleModule from 'ts-eslint'; +import { SourceCode } from 'ts-eslint'; /** * Check if the context file name is *.ts or *.tsx @@ -63,3 +64,48 @@ export function getNameFromPropertyName( } return `${propertyName.value}`; } + +/** Return true if both parameters are equal. */ +export type Equal = (a: T, b: T) => boolean; + +export function arraysAreEqual( + a: T[] | undefined, + b: T[] | undefined, + eq: (a: T, b: T) => boolean, +): boolean { + return ( + a === b || + (a !== undefined && + b !== undefined && + a.length === b.length && + a.every((x, idx) => eq(x, b[idx]))) + ); +} + +/** + * Gets a string name representation of the name of the given MethodDefinition + * or ClassProperty node, with handling for computed property names. + */ +export function getNameFromClassMember( + methodDefinition: TSESTree.MethodDefinition | TSESTree.ClassProperty, + sourceCode: SourceCode, +): string { + if (keyCanBeReadAsPropertyName(methodDefinition.key)) { + return getNameFromPropertyName(methodDefinition.key); + } + + return sourceCode.text.slice(...methodDefinition.key.range); +} + +/** + * This covers both actual property names, as well as computed properties that are either + * an identifier or a literal at the top level. + */ +function keyCanBeReadAsPropertyName( + node: TSESTree.Expression, +): node is TSESTree.PropertyName { + return ( + node.type === AST_NODE_TYPES.Literal || + node.type === AST_NODE_TYPES.Identifier + ); +} diff --git a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts index 01680fef838..c040362a128 100644 --- a/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts +++ b/packages/eslint-plugin/tests/rules/adjacent-overload-signatures.test.ts @@ -217,6 +217,11 @@ class Test { // examples from https://github.com/nzakas/eslint-plugin-typescript/issues/138 'export default function(foo : T) {}', 'export default function named(foo : T) {}', + ` +interface Foo { + [Symbol.toStringTag](): void; + [Symbol.iterator](): void; +}`, ], invalid: [ { 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 0abc6000f18..45ff620d616 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 @@ -41,6 +41,7 @@ class Test { method(): void { return; } + arrow = (): string => 'arrow'; } `, }, @@ -53,6 +54,7 @@ function test() { `, }, { + filename: 'test.ts', code: `fn(() => {});`, options: [ { @@ -61,6 +63,7 @@ function test() { ], }, { + filename: 'test.ts', code: `fn(function() {});`, options: [ { @@ -69,6 +72,7 @@ function test() { ], }, { + filename: 'test.ts', code: `[function() {}, () => {}]`, options: [ { @@ -77,6 +81,7 @@ function test() { ], }, { + filename: 'test.ts', code: `(function() {});`, options: [ { @@ -85,6 +90,7 @@ function test() { ], }, { + filename: 'test.ts', code: `(() => {})();`, options: [ { @@ -92,6 +98,28 @@ function test() { }, ], }, + { + filename: 'test.ts', + code: ` +var arrowFn: Foo = () => 'test'; + `, + options: [ + { + allowTypedFunctionExpressions: true, + }, + ], + }, + { + filename: 'test.ts', + code: ` +var funcExpr: Foo = function() { return 'test'; }; + `, + options: [ + { + allowTypedFunctionExpressions: true, + }, + ], + }, ], invalid: [ { @@ -149,6 +177,7 @@ class Test { method() { return; } + arrow = () => 'arrow'; } `, errors: [ @@ -162,6 +191,25 @@ class Test { line: 8, column: 9, }, + { + messageId: 'missingReturnType', + line: 11, + column: 11, + }, + ], + }, + { + filename: 'test.ts', + code: `function test() { + return; + }`, + options: [{ allowExpressions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 1, + column: 1, + }, ], }, { @@ -188,5 +236,29 @@ class Test { }, ], }, + { + filename: 'test.ts', + code: `var arrowFn = () => 'test';`, + options: [{ allowTypedFunctionExpressions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 1, + column: 15, + }, + ], + }, + { + filename: 'test.ts', + code: `var funcExpr = function() { return 'test'; };`, + options: [{ allowTypedFunctionExpressions: true }], + errors: [ + { + messageId: 'missingReturnType', + line: 1, + column: 16, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts b/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts index e630450689c..51a94a19de2 100644 --- a/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts +++ b/packages/eslint-plugin/tests/rules/explicit-member-accessibility.test.ts @@ -39,6 +39,129 @@ class Test { } `, }, + { + filename: 'test.ts', + code: ` +class Test { + protected name: string + protected foo?: string + public getX () { + return this.x + } +} + `, + options: [{ accessibility: 'explicit' }], + }, + { + filename: 'test.ts', + code: ` +class Test { + protected name: string + protected foo?: string + getX () { + return this.x + } +} + `, + options: [{ accessibility: 'no-public' }], + }, + { + filename: 'test.ts', + code: ` +class Test { + name: string + foo?: string + getX () { + return this.x + } + get fooName(): string { + return this.foo + ' ' + this.name + } +} + `, + options: [{ accessibility: 'no-public' }], + }, + { + filename: 'test.ts', + code: ` +class Test { + private x: number; + constructor (x: number) { + this.x = x; + } + get internalValue() { + return this.x; + } + private set internalValue(value: number) { + this.x = value; + } + public square (): number { + return this.x * this.x; + } +} + `, + options: [{ overrides: { constructors: 'off', accessors: 'off' } }], + }, + { + filename: 'test.ts', + code: ` +class Test { + private x: number; + public constructor (x: number) { + this.x = x; + } + public get internalValue() { + return this.x; + } + public set internalValue(value: number) { + this.x = value; + } + public square (): number { + return this.x * this.x; + } + half (): number { + return this.x / 2; + } +} + `, + options: [{ overrides: { methods: 'off' } }], + }, + { + filename: 'test.ts', + code: ` +class Test { + constructor(private x: number){} +} + `, + options: [{ accessibility: 'no-public' }], + }, + { + filename: 'test.ts', + code: ` +class Test { + constructor(public x: number){} +} + `, + options: [ + { + accessibility: 'no-public', + overrides: { parameterProperties: 'off' }, + }, + ], + }, + { + filename: 'test.js', + code: ` +class Test { + constructor(public x: number){} +} + `, + options: [ + { + accessibility: 'no-public', + }, + ], + }, ], invalid: [ { @@ -116,5 +239,204 @@ class Test { }, ], }, + { + filename: 'test.ts', + code: ` +class Test { + protected name: string + protected foo?: string + public getX () { + return this.x + } +} + `, + options: [{ accessibility: 'no-public' }], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + data: { + type: 'method definition', + name: 'getX', + }, + line: 5, + column: 3, + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Test { + protected name: string + public foo?: string + getX () { + return this.x + } +} + `, + options: [{ accessibility: 'no-public' }], + errors: [ + { + messageId: 'unwantedPublicAccessibility', + data: { + type: 'class property', + name: 'foo', + }, + line: 4, + column: 3, + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Test { + public x: number + public getX () { + return this.x + } +} + `, + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 3, + }, + { + messageId: 'unwantedPublicAccessibility', + line: 4, + column: 3, + }, + ], + options: [{ accessibility: 'no-public' }], + }, + { + filename: 'test.ts', + code: ` +class Test { + private x: number; + constructor (x: number) { + this.x = x; + } + get internalValue() { + return this.x; + } + set internalValue(value: number) { + this.x = value; + } +} + `, + errors: [ + { + messageId: 'missingAccessibility', + line: 7, + column: 3, + }, + { + messageId: 'missingAccessibility', + line: 10, + column: 3, + }, + ], + options: [{ overrides: { constructors: 'no-public' } }], + }, + { + filename: 'test.ts', + code: ` +class Test { + private x: number; + constructor (x: number) { + this.x = x; + } + get internalValue() { + return this.x; + } + set internalValue(value: number) { + this.x = value; + } +} + `, + errors: [ + { + messageId: 'missingAccessibility', + line: 4, + column: 3, + }, + { + messageId: 'missingAccessibility', + line: 7, + column: 3, + }, + { + messageId: 'missingAccessibility', + line: 10, + column: 3, + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Test { + constructor(public x: number){} +} + `, + errors: [ + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 15, + }, + ], + options: [ + { + accessibility: 'no-public', + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Test { + constructor(public x: number){} + public foo(): string { + return 'foo'; + } +} + `, + errors: [ + { + messageId: 'missingAccessibility', + line: 3, + column: 3, + }, + { + messageId: 'unwantedPublicAccessibility', + line: 3, + column: 15, + }, + ], + options: [ + { + overrides: { parameterProperties: 'no-public' }, + }, + ], + }, + { + filename: 'test.ts', + code: ` +class Test { + constructor(public x: number){} +} + `, + errors: [ + { + messageId: 'missingAccessibility', + line: 3, + column: 3, + }, + ], + }, ], }); diff --git a/packages/eslint-plugin/tests/rules/indent.test.ts b/packages/eslint-plugin/tests/rules/indent.test.ts index d845a871172..7fb18428d9f 100644 --- a/packages/eslint-plugin/tests/rules/indent.test.ts +++ b/packages/eslint-plugin/tests/rules/indent.test.ts @@ -742,6 +742,30 @@ const foo : Foo = { `, options: [4, { VariableDeclarator: { const: 3 } }], }, + { + code: ` +const name: string = ' Typescript ' + .toUpperCase() + .trim(), + + greeting: string = (" Hello " + name) + .toUpperCase() + .trim(); + `, + options: [2, { VariableDeclarator: { const: 3 } }], + }, + { + code: ` +const div: JQuery = $('
') + .addClass('some-class') + .appendTo($('body')), + + button: JQuery = $('