diff --git a/packages/eslint-plugin/README.md b/packages/eslint-plugin/README.md index 4d3e4c308e8..05d0a88ebcd 100644 --- a/packages/eslint-plugin/README.md +++ b/packages/eslint-plugin/README.md @@ -157,7 +157,6 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/no-non-null-assertion`](./docs/rules/no-non-null-assertion.md) | Disallows non-null assertions using the `!` postfix operator | :heavy_check_mark: | | | | [`@typescript-eslint/no-object-literal-type-assertion`](./docs/rules/no-object-literal-type-assertion.md) | Forbids an object literal to appear in a type assertion expression | :heavy_check_mark: | | | | [`@typescript-eslint/no-parameter-properties`](./docs/rules/no-parameter-properties.md) | Disallow the use of parameter properties in class constructors | :heavy_check_mark: | | | -| [`@typescript-eslint/no-reference-import`](./docs/rules/no-reference-import.md) | Disallow simultaneous use of `/// ` comments and ES6 style imports for the same module | | | | | [`@typescript-eslint/no-require-imports`](./docs/rules/no-require-imports.md) | Disallows invocation of `require()` | | | | | [`@typescript-eslint/no-this-alias`](./docs/rules/no-this-alias.md) | Disallow aliasing `this` | | | | | [`@typescript-eslint/no-triple-slash-reference`](./docs/rules/no-triple-slash-reference.md) | Disallow `/// ` comments | :heavy_check_mark: | | | @@ -178,6 +177,7 @@ Then you should add `airbnb` (or `airbnb-base`) to your `extends` section of `.e | [`@typescript-eslint/require-array-sort-compare`](./docs/rules/require-array-sort-compare.md) | Enforce giving `compare` argument to `Array#sort` | | | :thought_balloon: | | [`@typescript-eslint/restrict-plus-operands`](./docs/rules/restrict-plus-operands.md) | When adding two variables, operands must both be of type number or of type string | | | :thought_balloon: | | [`@typescript-eslint/semi`](./docs/rules/semi.md) | Require or disallow semicolons instead of ASI | | :wrench: | | +| [`@typescript-eslint/triple-slash-reference`](./docs/rules/triple-slash-reference.md) | Sets preference level for triple slash directives versus ES6-style import declarations | | | | | [`@typescript-eslint/type-annotation-spacing`](./docs/rules/type-annotation-spacing.md) | Require consistent spacing around type annotations | :heavy_check_mark: | :wrench: | | | [`@typescript-eslint/unbound-method`](./docs/rules/unbound-method.md) | Enforces unbound methods are called with their expected scope | | | :thought_balloon: | | [`@typescript-eslint/unified-signatures`](./docs/rules/unified-signatures.md) | Warns for any two overloads that could be unified into one by using a union or an optional/rest parameter | | | | diff --git a/packages/eslint-plugin/docs/rules/no-reference-import.md b/packages/eslint-plugin/docs/rules/no-reference-import.md deleted file mode 100644 index 05a7db9dcae..00000000000 --- a/packages/eslint-plugin/docs/rules/no-reference-import.md +++ /dev/null @@ -1,37 +0,0 @@ -# Disallow simultaneous use of `/// ` comments and ES6 style imports for the same module. (no-reference-import) - -Use of triple-slash directives is discouraged in favor of the newer `import` style. In cases where both styles might occur, this rule prevents use of triple-slash references for modules which are otherwise imported. - -Use `no-triple-slash-reference` instead if you intend to ban triple slash directives entirely. - -## Rule Details - -Examples of **incorrect** code for this rule: - -```ts -/// -import * as foo from 'foo'; -``` - -```ts -/// -import foo = require('foo'); -``` - -Examples of **correct** code for this rule: - -```ts -import * as foo from 'foo'; -``` - -```ts -import foo = require('foo'); -``` - -## When To Use It - -Any time you might use triple-slash directives and ES6 import declarations in the same file. - -## When Not To Use It - -If you intend to ban triple slash directives entirely. diff --git a/packages/eslint-plugin/docs/rules/triple-slash-reference.md b/packages/eslint-plugin/docs/rules/triple-slash-reference.md new file mode 100644 index 00000000000..b09fc741097 --- /dev/null +++ b/packages/eslint-plugin/docs/rules/triple-slash-reference.md @@ -0,0 +1,58 @@ +# Sets preference level for triple slash directives versus ES6-style import declarations. (triple-slash-reference) + +Use of triple-slash reference type directives is discouraged in favor of the newer `import` style. This rule allows you to ban use of `/// `, `/// `, or `/// ` directives. + +If you use the `no-triple-slash-reference` rule, consider using this rule instead. + +## Rule Details + +With `{ "path": "never", "types": "never", "lib": "never" }` options set, the following will all be **incorrect** usage: + +```ts +/// +/// +/// +``` + +Examples of **incorrect** code for the `{ "types": "prefer-import" }` option. Note that these are only errors when **both** stlyes are used for the **same** module: + +```ts +/// +import * as foo from 'foo'; +``` + +```ts +/// +import foo = require('foo'); +``` + +With `{ "path": "always", "types": "always", "lib": "always" }` options set, the following will all be **correct** usage: + +```ts +/// +/// +/// +``` + +Examples of **correct** code for the `{ "types": "prefer-import" }` option: + +```ts +import * as foo from 'foo'; +``` + +```ts +import foo = require('foo'); +``` + +## When To Use It + +If you want to ban use of one or all of the triple slash reference directives, or any time you might use triple-slash type reference directives and ES6 import declarations in the same file. + +## When Not To Use It + +If you want to use all flavors of triple slash reference directives. + +## Compatibility + +- TSLint: [no-reference](http://palantir.github.io/tslint/rules/no-reference/) +- TSLint: [no-reference-import](https://palantir.github.io/tslint/rules/no-reference-import/) diff --git a/packages/eslint-plugin/src/rules/index.ts b/packages/eslint-plugin/src/rules/index.ts index d12e6542a10..2c16956758f 100644 --- a/packages/eslint-plugin/src/rules/index.ts +++ b/packages/eslint-plugin/src/rules/index.ts @@ -31,7 +31,6 @@ import noNamespace from './no-namespace'; import noNonNullAssertion from './no-non-null-assertion'; import noObjectLiteralTypeAssertion from './no-object-literal-type-assertion'; import noParameterProperties from './no-parameter-properties'; -import noReferenceImport from './no-reference-import'; import noRequireImports from './no-require-imports'; import noThisAlias from './no-this-alias'; import noTripleSlashReference from './no-triple-slash-reference'; @@ -53,6 +52,7 @@ import promiseFunctionAsync from './promise-function-async'; import requireArraySortCompare from './require-array-sort-compare'; import restrictPlusOperands from './restrict-plus-operands'; import semi from './semi'; +import tripleSlashReference from './triple-slash-reference'; import typeAnnotationSpacing from './type-annotation-spacing'; import unboundMethod from './unbound-method'; import unifiedSignatures from './unified-signatures'; @@ -91,7 +91,6 @@ export default { 'no-non-null-assertion': noNonNullAssertion, 'no-object-literal-type-assertion': noObjectLiteralTypeAssertion, 'no-parameter-properties': noParameterProperties, - 'no-reference-import': noReferenceImport, 'no-require-imports': noRequireImports, 'no-this-alias': noThisAlias, 'no-triple-slash-reference': noTripleSlashReference, @@ -113,6 +112,7 @@ export default { 'require-array-sort-compare': requireArraySortCompare, 'restrict-plus-operands': restrictPlusOperands, semi: semi, + 'triple-slash-reference': tripleSlashReference, 'type-annotation-spacing': typeAnnotationSpacing, 'unbound-method': unboundMethod, 'unified-signatures': unifiedSignatures, diff --git a/packages/eslint-plugin/src/rules/no-reference-import.ts b/packages/eslint-plugin/src/rules/no-reference-import.ts deleted file mode 100644 index 7a7fee23a22..00000000000 --- a/packages/eslint-plugin/src/rules/no-reference-import.ts +++ /dev/null @@ -1,78 +0,0 @@ -import * as util from '../util'; -import { - Literal, - Node, - TSExternalModuleReference, -} from '@typescript-eslint/typescript-estree/dist/ts-estree/ts-estree'; -import { TSESTree } from '@typescript-eslint/typescript-estree'; - -export default util.createRule({ - name: 'no-reference-import', - meta: { - type: 'suggestion', - docs: { - description: - 'Disallow simultaneous use of `/// ` comments and ES6 style imports for the same module', - category: 'Best Practices', - recommended: false, - }, - schema: [], - messages: { - noReferenceImport: 'Do not reference {{module}} if importing it anyway.', - }, - }, - defaultOptions: [], - create(context) { - let programNode: Node; - const sourceCode = context.getSourceCode(); - const references: ({ - comment: TSESTree.Comment; - importName: string; - })[] = []; - - function hasMatchingReference(source: Literal) { - references.forEach(reference => { - if (reference.importName === source.value) { - context.report({ - node: reference.comment, - messageId: 'noReferenceImport', - data: { - module: reference.importName, - }, - }); - } - }); - } - return { - ImportDeclaration(node) { - if (programNode) { - const source = node.source as Literal; - hasMatchingReference(source); - } - }, - TSImportEqualsDeclaration(node) { - if (programNode) { - const source = (node.moduleReference as TSExternalModuleReference) - .expression as Literal; - hasMatchingReference(source); - } - }, - Program(node) { - programNode = node; - const referenceRegExp = /^\/\s* { - if (comment.type !== 'Line') { - return; - } - const referenceResult = referenceRegExp.exec(comment.value); - - if (referenceResult && referenceResult[1]) { - references.push({ comment, importName: referenceResult[1] }); - } - }); - }, - }; - }, -}); diff --git a/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts b/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts index c7780a99bf3..5d24bb48e2c 100644 --- a/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts +++ b/packages/eslint-plugin/src/rules/no-triple-slash-reference.ts @@ -11,7 +11,7 @@ export default util.createRule({ }, schema: [], messages: { - tripleSlashReference: 'Do not use a triple slash reference.', + noTripleSlashReference: 'Do not use a triple slash reference.', }, }, defaultOptions: [], @@ -30,7 +30,7 @@ export default util.createRule({ if (referenceRegExp.test(comment.value)) { context.report({ node: comment, - messageId: 'tripleSlashReference', + messageId: 'noTripleSlashReference', }); } }); diff --git a/packages/eslint-plugin/src/rules/triple-slash-reference.ts b/packages/eslint-plugin/src/rules/triple-slash-reference.ts new file mode 100644 index 00000000000..286f445c966 --- /dev/null +++ b/packages/eslint-plugin/src/rules/triple-slash-reference.ts @@ -0,0 +1,129 @@ +import * as util from '../util'; +import { + Literal, + Node, + TSExternalModuleReference, +} from '@typescript-eslint/typescript-estree/dist/ts-estree/ts-estree'; +import { TSESTree } from '@typescript-eslint/typescript-estree'; + +type Options = [ + { + lib?: 'always' | 'never'; + path?: 'always' | 'never'; + types?: 'always' | 'never' | 'prefer-import'; + } +]; +type MessageIds = 'tripleSlashReference'; + +export default util.createRule({ + name: 'triple-slash-reference', + meta: { + type: 'suggestion', + docs: { + description: + 'Sets preference level for triple slash directives versus ES6-style import declarations', + category: 'Best Practices', + recommended: false, + }, + messages: { + tripleSlashReference: + 'Do not use a triple slash reference for {{module}}, use `import` style instead.', + }, + schema: [ + { + type: 'object', + properties: { + lib: { + enum: ['always', 'never'], + }, + path: { + enum: ['always', 'never'], + }, + types: { + enum: ['always', 'never', 'prefer-import'], + }, + }, + additionalProperties: false, + }, + ], + }, + defaultOptions: [ + { + lib: 'always', + path: 'never', + types: 'prefer-import', + }, + ], + create(context, [{ lib, path, types }]) { + let programNode: Node; + const sourceCode = context.getSourceCode(); + const references: ({ + comment: TSESTree.Comment; + importName: string; + })[] = []; + + function hasMatchingReference(source: Literal) { + references.forEach(reference => { + if (reference.importName === source.value) { + context.report({ + node: reference.comment, + messageId: 'tripleSlashReference', + data: { + module: reference.importName, + }, + }); + } + }); + } + return { + ImportDeclaration(node) { + if (programNode) { + const source = node.source as Literal; + hasMatchingReference(source); + } + }, + TSImportEqualsDeclaration(node) { + if (programNode) { + const source = (node.moduleReference as TSExternalModuleReference) + .expression as Literal; + hasMatchingReference(source); + } + }, + Program(node) { + if (lib === 'always' && path === 'always' && types == 'always') { + return; + } + programNode = node; + const referenceRegExp = /^\/\s* { + if (comment.type !== 'Line') { + return; + } + const referenceResult = referenceRegExp.exec(comment.value); + + if (referenceResult) { + if ( + (referenceResult[1] === 'types' && types === 'never') || + (referenceResult[1] === 'path' && path === 'never') || + (referenceResult[1] === 'lib' && lib === 'never') + ) { + context.report({ + node: comment, + messageId: 'tripleSlashReference', + data: { + module: referenceResult[2], + }, + }); + return; + } + if (referenceResult[1] === 'types' && types === 'prefer-import') { + references.push({ comment, importName: referenceResult[2] }); + } + } + }); + }, + }; + }, +}); diff --git a/packages/eslint-plugin/tests/rules/no-reference-import.test.ts b/packages/eslint-plugin/tests/rules/no-reference-import.test.ts deleted file mode 100644 index 20ddd2b94c6..00000000000 --- a/packages/eslint-plugin/tests/rules/no-reference-import.test.ts +++ /dev/null @@ -1,49 +0,0 @@ -import rule from '../../src/rules/no-reference-import'; -import { RuleTester } from '../RuleTester'; - -const ruleTester = new RuleTester({ - parserOptions: { - sourceType: 'module', - }, - parser: '@typescript-eslint/parser', -}); - -ruleTester.run('no-reference-import', rule, { - valid: [ - `/// - var foo = require("foo");`, - `/// - import * as foo from "foo"`, - ], - invalid: [ - { - code: ` -/// -import * as foo from "foo" - `, - - parser: '@typescript-eslint/parser', - errors: [ - { - messageId: 'noReferenceImport', - line: 2, - column: 1, - }, - ], - }, - { - code: ` -/// -import foo = require("foo"); - `, - parser: '@typescript-eslint/parser', - errors: [ - { - messageId: 'noReferenceImport', - line: 2, - column: 1, - }, - ], - }, - ], -}); diff --git a/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts b/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts index 5beaf104e52..f120d526243 100644 --- a/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts +++ b/packages/eslint-plugin/tests/rules/no-triple-slash-reference.test.ts @@ -22,7 +22,7 @@ let a code: '/// ', errors: [ { - messageId: 'tripleSlashReference', + messageId: 'noTripleSlashReference', line: 1, column: 1, }, @@ -36,7 +36,7 @@ let a parser: '@typescript-eslint/parser', errors: [ { - messageId: 'tripleSlashReference', + messageId: 'noTripleSlashReference', line: 2, column: 1, }, diff --git a/packages/eslint-plugin/tests/rules/triple-slash-reference.test.ts b/packages/eslint-plugin/tests/rules/triple-slash-reference.test.ts new file mode 100644 index 00000000000..2e235f1751c --- /dev/null +++ b/packages/eslint-plugin/tests/rules/triple-slash-reference.test.ts @@ -0,0 +1,122 @@ +import rule from '../../src/rules/triple-slash-reference'; +import { RuleTester } from '../RuleTester'; + +const ruleTester = new RuleTester({ + parserOptions: { + sourceType: 'module', + }, + parser: '@typescript-eslint/parser', +}); + +ruleTester.run('triple-slash-reference', rule, { + valid: [ + { + code: ` + /// + import * as foo from "foo" + `, + options: [{ path: 'always' }], + }, + { + code: ` + /// + import * as foo from "foo" + `, + options: [{ types: 'always' }], + }, + { + code: ` + /// + import * as foo from "foo" + `, + options: [{ lib: 'always' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ path: 'never' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ types: 'never' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ lib: 'never' }], + }, + { + code: ` + import * as foo from "foo" + `, + options: [{ types: 'prefer-import' }], + }, + ], + invalid: [ + { + code: ` +/// +import * as foo from "foo" + `, + options: [{ types: 'prefer-import' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 2, + column: 1, + }, + ], + }, + { + code: ` +/// +import foo = require("foo"); + `, + options: [{ types: 'prefer-import' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 2, + column: 1, + }, + ], + }, + { + code: `/// `, + options: [{ path: 'never' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 1, + column: 1, + }, + ], + }, + { + code: `/// `, + options: [{ types: 'never' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 1, + column: 1, + }, + ], + }, + { + code: `/// `, + options: [{ lib: 'never' }], + errors: [ + { + messageId: 'tripleSlashReference', + line: 1, + column: 1, + }, + ], + }, + ], +});