From 3ca0e7ea8b0dd24772d1210b0e52d1dcaa4dbee3 Mon Sep 17 00:00:00 2001 From: =?UTF-8?q?=E6=9B=BE=E6=98=8E=E5=81=A5?= Date: Fri, 19 May 2023 16:29:54 +0800 Subject: [PATCH] feat(ts): ban cjs exports in ts file (#167) --- packages/eslint-config-ts/index.js | 2 + packages/eslint-plugin-antfu/src/index.ts | 4 ++ .../src/rules/no-cjs-exports.test.ts | 28 +++++++++++++ .../src/rules/no-cjs-exports.ts | 41 +++++++++++++++++++ .../src/rules/no-ts-export-equal.test.ts | 26 ++++++++++++ .../src/rules/no-ts-export-equal.ts | 35 ++++++++++++++++ 6 files changed, 136 insertions(+) create mode 100644 packages/eslint-plugin-antfu/src/rules/no-cjs-exports.test.ts create mode 100644 packages/eslint-plugin-antfu/src/rules/no-cjs-exports.ts create mode 100644 packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.test.ts create mode 100644 packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.ts diff --git a/packages/eslint-config-ts/index.js b/packages/eslint-config-ts/index.js index 31c4b70..eba1141 100644 --- a/packages/eslint-config-ts/index.js +++ b/packages/eslint-config-ts/index.js @@ -158,6 +158,8 @@ module.exports = { // antfu 'antfu/generic-spacing': 'error', + 'antfu/no-cjs-exports': 'error', + 'antfu/no-ts-export-equal': 'error', // off '@typescript-eslint/consistent-indexed-object-style': 'off', diff --git a/packages/eslint-plugin-antfu/src/index.ts b/packages/eslint-plugin-antfu/src/index.ts index 9d3774c..c330a4a 100644 --- a/packages/eslint-plugin-antfu/src/index.ts +++ b/packages/eslint-plugin-antfu/src/index.ts @@ -3,6 +3,8 @@ import ifNewline from './rules/if-newline' import importDedupe from './rules/import-dedupe' import preferInlineTypeImport from './rules/prefer-inline-type-import' import topLevelFunction from './rules/top-level-function' +import noTsExportEqual from './rules/no-ts-export-equal' +import noCjsExports from './rules/no-cjs-exports' export default { rules: { @@ -11,5 +13,7 @@ export default { 'prefer-inline-type-import': preferInlineTypeImport, 'generic-spacing': genericSpacing, 'top-level-function': topLevelFunction, + 'no-cjs-exports': noCjsExports, + 'no-ts-export-equal': noTsExportEqual, }, } diff --git a/packages/eslint-plugin-antfu/src/rules/no-cjs-exports.test.ts b/packages/eslint-plugin-antfu/src/rules/no-cjs-exports.test.ts new file mode 100644 index 0000000..5adfd34 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/no-cjs-exports.test.ts @@ -0,0 +1,28 @@ +import { RuleTester } from '@typescript-eslint/utils/dist/ts-eslint' +import { it } from 'vitest' +import rule, { RULE_NAME } from './no-cjs-exports' + +const valids = [ + { code: 'export = {}', filename: 'test.ts' }, + { code: 'exports.a = {}', filename: 'test.js' }, + { code: 'module.exports.a = {}', filename: 'test.js' }, +] + +const invalids = [ + { code: 'exports.a = {}', filename: 'test.ts' }, + { code: 'module.exports.a = {}', filename: 'test.ts' }, +] + +it('runs', () => { + const ruleTester: RuleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + }) + + ruleTester.run(RULE_NAME, rule, { + valid: valids, + invalid: invalids.map(i => ({ + ...i, + errors: [{ messageId: 'noCjsExports' }], + })), + }) +}) diff --git a/packages/eslint-plugin-antfu/src/rules/no-cjs-exports.ts b/packages/eslint-plugin-antfu/src/rules/no-cjs-exports.ts new file mode 100644 index 0000000..742a4a3 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/no-cjs-exports.ts @@ -0,0 +1,41 @@ +import { createEslintRule } from '../utils' + +export const RULE_NAME = 'no-cjs-exports' +export type MessageIds = 'noCjsExports' +export type Options = [] + +export default createEslintRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Do not use CJS exports', + recommended: false, + }, + schema: [], + messages: { + noCjsExports: 'Use ESM export instead', + }, + }, + defaultOptions: [], + create: (context) => { + const extension = context.getFilename().split('.').pop() + if (!['ts', 'tsx', 'mts', 'cts'].includes(extension)) + return {} + + return { + 'MemberExpression[object.name="exports"]': function (node) { + context.report({ + node, + messageId: 'noCjsExports', + }) + }, + 'MemberExpression[object.name="module"][property.name="exports"]': function (node) { + context.report({ + node, + messageId: 'noCjsExports', + }) + }, + } + }, +}) diff --git a/packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.test.ts b/packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.test.ts new file mode 100644 index 0000000..06dcfe0 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.test.ts @@ -0,0 +1,26 @@ +import { RuleTester } from '@typescript-eslint/utils/dist/ts-eslint' +import { it } from 'vitest' +import rule, { RULE_NAME } from './no-ts-export-equal' + +const valids = [ + { code: 'export default {}', filename: 'test.ts' }, + { code: 'export = {}', filename: 'test.js' }, +] + +const invalids = [ + { code: 'export = {}', filename: 'test.ts' }, +] + +it('runs', () => { + const ruleTester: RuleTester = new RuleTester({ + parser: require.resolve('@typescript-eslint/parser'), + }) + + ruleTester.run(RULE_NAME, rule, { + valid: valids, + invalid: invalids.map(i => ({ + ...i, + errors: [{ messageId: 'noTsExportEqual' }], + })), + }) +}) diff --git a/packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.ts b/packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.ts new file mode 100644 index 0000000..ca578e7 --- /dev/null +++ b/packages/eslint-plugin-antfu/src/rules/no-ts-export-equal.ts @@ -0,0 +1,35 @@ +import { createEslintRule } from '../utils' + +export const RULE_NAME = 'no-ts-export-equal' +export type MessageIds = 'noTsExportEqual' +export type Options = [] + +export default createEslintRule({ + name: RULE_NAME, + meta: { + type: 'problem', + docs: { + description: 'Do not use `exports =`', + recommended: false, + }, + schema: [], + messages: { + noTsExportEqual: 'Use ESM `export default` instead', + }, + }, + defaultOptions: [], + create: (context) => { + const extension = context.getFilename().split('.').pop() + if (!['ts', 'tsx', 'mts', 'cts'].includes(extension)) + return {} + + return { + TSExportAssignment(node) { + context.report({ + node, + messageId: 'noTsExportEqual', + }) + }, + } + }, +})