diff --git a/package-lock.json b/package-lock.json index 51953bc..6430ac9 100644 --- a/package-lock.json +++ b/package-lock.json @@ -24,6 +24,7 @@ "eslint-plugin-promise": "^4.2.1", "eslint-plugin-react": "^7.20.3", "eslint-plugin-react-hooks": "^4.0.8", + "eslint-plugin-simple-import-sort": "^7.0.0", "eslint-plugin-sort-class-members": "^1.7.0", "eslint-teamcity": "^2.2.0", "lodash": "^4.17.15", @@ -9347,6 +9348,14 @@ "url": "https://github.com/sponsors/ljharb" } }, + "node_modules/eslint-plugin-simple-import-sort": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-7.0.0.tgz", + "integrity": "sha512-U3vEDB5zhYPNfxT5TYR7u01dboFZp+HNpnGhkDB2g/2E4wZ/g1Q9Ton8UwCLfRV9yAKyYqDh62oHOamvkFxsvw==", + "peerDependencies": { + "eslint": ">=5.0.0" + } + }, "node_modules/eslint-plugin-sort-class-members": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sort-class-members/-/eslint-plugin-sort-class-members-1.11.0.tgz", @@ -28169,6 +28178,11 @@ "resolved": "https://registry.npmjs.org/eslint-plugin-react-hooks/-/eslint-plugin-react-hooks-4.3.0.tgz", "integrity": "sha512-XslZy0LnMn+84NEG9jSGR6eGqaZB3133L8xewQo3fQagbQuGt7a63gf+P1NGKZavEYEC3UXaWEAA/AqDkuN6xA==" }, + "eslint-plugin-simple-import-sort": { + "version": "7.0.0", + "resolved": "https://registry.npmjs.org/eslint-plugin-simple-import-sort/-/eslint-plugin-simple-import-sort-7.0.0.tgz", + "integrity": "sha512-U3vEDB5zhYPNfxT5TYR7u01dboFZp+HNpnGhkDB2g/2E4wZ/g1Q9Ton8UwCLfRV9yAKyYqDh62oHOamvkFxsvw==" + }, "eslint-plugin-sort-class-members": { "version": "1.11.0", "resolved": "https://registry.npmjs.org/eslint-plugin-sort-class-members/-/eslint-plugin-sort-class-members-1.11.0.tgz", diff --git a/packages/eslint-config-angular/internal/import.js b/packages/eslint-config-angular/internal/import.js index c9a212f..477a0db 100644 --- a/packages/eslint-config-angular/internal/import.js +++ b/packages/eslint-config-angular/internal/import.js @@ -1,10 +1,26 @@ module.exports = { parser: '@typescript-eslint/parser', + plugins: ['@typescript-eslint', 'simple-import-sort', 'eslint-plugin-import'], settings: { 'import/parsers': { '@typescript-eslint/parser': ['.ts'] }, 'import/resolver': { 'eslint-import-resolver-typescript': true }, }, rules: { + 'simple-import-sort/imports': 'error', + 'simple-import-sort/exports': 'error', + + 'import/first': 'error', + 'import/exports-last': 'off', + 'import/no-default-export': 'off', + 'import/newline-after-import': ['error', { count: 1 }], 'import/no-webpack-loader-syntax': 'off', + + /** + * @note: note you must disable the base rule + * as it can report incorrect errors in @typescript-eslint + */ + 'import/no-duplicates': 'off', + 'no-duplicate-imports': 'off', + '@typescript-eslint/no-duplicate-imports': 'error', }, }; diff --git a/packages/eslint-config-angular/package.json b/packages/eslint-config-angular/package.json index 4251da7..f992482 100644 --- a/packages/eslint-config-angular/package.json +++ b/packages/eslint-config-angular/package.json @@ -23,6 +23,9 @@ "type": "git", "url": "https://github.com/TinkoffCreditSystems/linters.git" }, + "dependencies": { + "eslint-plugin-simple-import-sort": "^7.0.0" + }, "peerDependencies": { "@tinkoff/eslint-config": "^1.13.2", "@typescript-eslint/eslint-plugin": "^5.4.0", diff --git a/packages/eslint-config-angular/test/imports/__fixtures__/imports-happy.fixture.ts b/packages/eslint-config-angular/test/imports/__fixtures__/imports-happy.fixture.ts new file mode 100644 index 0000000..a13b296 --- /dev/null +++ b/packages/eslint-config-angular/test/imports/__fixtures__/imports-happy.fixture.ts @@ -0,0 +1,8 @@ +import { rules as _TSLint } from '@typescript-eslint/eslint-plugin'; +import { process as _process } from 'babel-jest'; + +import { _A as A, _B as B } from './shared-imports.fixture'; + +// only for eslint testing +new A(_TSLint); +new B(_process); diff --git a/packages/eslint-config-angular/test/imports/__fixtures__/imports-unhappy.fixture.ts b/packages/eslint-config-angular/test/imports/__fixtures__/imports-unhappy.fixture.ts new file mode 100644 index 0000000..a6a1c51 --- /dev/null +++ b/packages/eslint-config-angular/test/imports/__fixtures__/imports-unhappy.fixture.ts @@ -0,0 +1,7 @@ +import { rules as _TSLint } from '@typescript-eslint/eslint-plugin'; +import { process as _process } from 'babel-jest'; +import { _A as A, _B as B } from './shared-imports.fixture'; + +// only for eslint testing +new A(_TSLint); +new B(_process); diff --git a/packages/eslint-config-angular/test/imports/__fixtures__/shared-imports.fixture.ts b/packages/eslint-config-angular/test/imports/__fixtures__/shared-imports.fixture.ts new file mode 100644 index 0000000..0369145 --- /dev/null +++ b/packages/eslint-config-angular/test/imports/__fixtures__/shared-imports.fixture.ts @@ -0,0 +1,6 @@ +export class _A { + constructor(public options: unknown) {} +} +export class _B { + constructor(public options: unknown) {} +} diff --git a/packages/eslint-config-angular/test/imports/__snapshots__/imports-happy.test.js.snap b/packages/eslint-config-angular/test/imports/__snapshots__/imports-happy.test.js.snap new file mode 100644 index 0000000..e807a9f --- /dev/null +++ b/packages/eslint-config-angular/test/imports/__snapshots__/imports-happy.test.js.snap @@ -0,0 +1,3 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`imports / happy path happy 1`] = `""`; diff --git a/packages/eslint-config-angular/test/imports/__snapshots__/imports-unhappy.test.js.snap b/packages/eslint-config-angular/test/imports/__snapshots__/imports-unhappy.test.js.snap new file mode 100644 index 0000000..7097174 --- /dev/null +++ b/packages/eslint-config-angular/test/imports/__snapshots__/imports-unhappy.test.js.snap @@ -0,0 +1,14 @@ +// Jest Snapshot v1, https://goo.gl/fbAQLP + +exports[`imports / unhappy path unhappy 1`] = ` +"error: Run autofix to sort these imports! (simple-import-sort/imports) at packages/eslint-config-angular/test/imports/__fixtures__/imports-unhappy.fixture.ts:1:1: +> 1 | import { rules as _TSLint } from '@typescript-eslint/eslint-plugin'; + | ^ + 2 | import { process as _process } from 'babel-jest'; + 3 | import { _A as A, _B as B } from './shared-imports.fixture'; + 4 | + + +1 error found. +1 error potentially fixable with the \`--fix\` option." +`; diff --git a/packages/eslint-config-angular/test/imports/imports-happy.test.js b/packages/eslint-config-angular/test/imports/imports-happy.test.js new file mode 100644 index 0000000..9783250 --- /dev/null +++ b/packages/eslint-config-angular/test/imports/imports-happy.test.js @@ -0,0 +1,21 @@ +import ESlint from 'eslint'; +import path from 'path'; + +describe('imports / happy path', () => { + const cli = new ESlint.CLIEngine({ + cwd: path.join(__dirname, '..'), + useEslintrc: false, + baseConfig: { + extends: ['../internal/base', '../internal/import'], + }, + }); + + it('happy', () => { + const codeframe = cli.getFormatter('codeframe'); + const report = cli.executeOnFiles([ + path.join(__dirname, './__fixtures__/imports-happy.fixture.ts'), + ]); + + expect(codeframe(report.results)).toMatchSnapshot(); + }); +}); diff --git a/packages/eslint-config-angular/test/imports/imports-unhappy.test.js b/packages/eslint-config-angular/test/imports/imports-unhappy.test.js new file mode 100644 index 0000000..f2ba157 --- /dev/null +++ b/packages/eslint-config-angular/test/imports/imports-unhappy.test.js @@ -0,0 +1,21 @@ +import ESlint from 'eslint'; +import path from 'path'; + +describe('imports / unhappy path', () => { + const cli = new ESlint.CLIEngine({ + cwd: path.join(__dirname, '..'), + useEslintrc: false, + baseConfig: { + extends: ['../internal/base', '../internal/import'], + }, + }); + + it('unhappy', () => { + const codeframe = cli.getFormatter('codeframe'); + const report = cli.executeOnFiles([ + path.join(__dirname, './__fixtures__/imports-unhappy.fixture.ts'), + ]); + + expect(codeframe(report.results)).toMatchSnapshot(); + }); +}); diff --git a/tsconfig.json b/tsconfig.json index 18bd260..5071a8c 100644 --- a/tsconfig.json +++ b/tsconfig.json @@ -1,6 +1,7 @@ { "compilerOptions": { "experimentalDecorators": true, + "moduleResolution": "node", "outDir": "tslint/rules", "module": "es6", "target": "es6",