Skip to content

Commit

Permalink
Added no-exported-types-in-tsx-files
Browse files Browse the repository at this point in the history
  • Loading branch information
oskarscholander committed Oct 23, 2024
1 parent 2bdfea6 commit aefc977
Show file tree
Hide file tree
Showing 7 changed files with 172 additions and 0 deletions.
6 changes: 6 additions & 0 deletions .prettierrc
Original file line number Diff line number Diff line change
@@ -0,0 +1,6 @@
{
"tabWidth": 4,
"useTabs": false,
"trailingComma": "es5",
"arrowParens": "always"
}
33 changes: 33 additions & 0 deletions README.md
Original file line number Diff line number Diff line change
Expand Up @@ -9,6 +9,7 @@ Custom ESLint rules used internally at Meitner
- [no-use-prefix-for-non-hook](#no-use-prefix-for-non-hook)
- [no-react-namespace](#no-react-namespace)
- [no-literal-jsx-style-prop-values](#no-literal-jsx-style-prop-values)
- [no-exported-types-outside-types-file](#no-exported-types-outside-types-file)

### no-inline-function-parameter-type-annotation

Expand Down Expand Up @@ -178,3 +179,35 @@ Examples of invalid code
```ts
<Component myProp={myValue} {...props} />
```

### no-exported-types-in-tsx-files

Exporting your types from your component's tsx file can lead to dependency loops and make your code harder to maintain.

This rule forbids exporting types from tsx files.

Examples of valid code

```ts
// MyComponent.tsx
import { Props } from "./MyComponent.types";

export default function MyComponent(props: Props) {
return <div>{props.children}</div>;
}
```

Examples of invalid code

```ts
// MyComponent.tsx
import { Props } from "./MyComponent.types";

export default function MyComponent(props: Props) { // error
return <div>{props.children}</div>;
}

export type Props = {
children: ReactNode;
};
```
4 changes: 4 additions & 0 deletions package.json
Original file line number Diff line number Diff line change
Expand Up @@ -19,6 +19,7 @@
},
"devDependencies": {
"@types/eslint": "^8.56.7",
"@types/node": "^22.7.9",
"@typescript-eslint/eslint-plugin": "^7.8.0",
"@typescript-eslint/parser": "^7.8.0",
"@typescript-eslint/rule-tester": "7.5.0",
Expand All @@ -30,5 +31,8 @@
},
"peerDependencies": {
"eslint": "8.56.0"
},
"dependencies": {
"prettier": "^3.3.3"
}
}
2 changes: 2 additions & 0 deletions src/rules/index.ts
Original file line number Diff line number Diff line change
@@ -1,4 +1,5 @@
import { alwaysSpreadJSXPropsFirst } from "./alwaysSpreadJSXPropsFirst";
import { noExportedTypesInTsxFiles } from "./noExportedTypesInTsxFiles";
import { noInlineFunctionParameterTypeAnnotation } from "./noInlineFunctionParameterTypeAnnotation";
import { noLiteralJSXStylePropValues } from "./noLiteralJSXStylePropValues";
import { noMixedExports } from "./noMixedExports";
Expand All @@ -13,6 +14,7 @@ const rules = {
"no-react-namespace": noReactNamespace,
"no-literal-jsx-style-prop-values": noLiteralJSXStylePropValues,
"always-spread-props-first": alwaysSpreadJSXPropsFirst,
"no-exported-types-in-tsx-files": noExportedTypesInTsxFiles,
};

export { rules };
40 changes: 40 additions & 0 deletions src/rules/noExportedTypesInTsxFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,40 @@
import { ESLintUtils, TSESTree } from "@typescript-eslint/utils";

export const noExportedTypesInTsxFiles = ESLintUtils.RuleCreator.withoutDocs({
create(context) {
const filename = context.filename;
const isTsxFile = filename.endsWith(".tsx");

return {
TSInterfaceDeclaration(node: TSESTree.TSInterfaceDeclaration) {
checkExportedType(node);
},
TSTypeAliasDeclaration(node: TSESTree.TSTypeAliasDeclaration) {
checkExportedType(node);
},
TSEnumDeclaration(node: TSESTree.TSEnumDeclaration) {
checkExportedType(node);
},
};

function checkExportedType(node: TSESTree.Node) {
const isExported = node.parent?.type === "ExportNamedDeclaration";

if (isExported && isTsxFile) {
context.report({
node,
messageId: "noExportedTypesInTsxFiles",
});
}
}
},
meta: {
type: "problem",
messages: {
noExportedTypesInTsxFiles:
"Exported types are not allowed in '.tsx' files.",
},
schema: [],
},
defaultOptions: [],
});
70 changes: 70 additions & 0 deletions src/tests/noExportedTypesInTsxFiles.ts
Original file line number Diff line number Diff line change
@@ -0,0 +1,70 @@
import { RuleTester } from "@typescript-eslint/rule-tester";
import * as vitest from "vitest";
import { noExportedTypesInTsxFiles } from "../rules/noExportedTypesInTsxFiles";

RuleTester.afterAll = vitest.afterAll;
RuleTester.it = vitest.it;
RuleTester.itOnly = vitest.it.only;
RuleTester.describe = vitest.describe;

const ruleTester = new RuleTester({
parser: "@typescript-eslint/parser",
});

ruleTester.run("noExportedTypesInTsxFiles", noExportedTypesInTsxFiles, {
valid: [
{
code: `
export interface MyInterface {
prop: string;
}
`,
filename: "types.ts",
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
},
{
code: `
interface MyInterface {
prop: string;
}
`,
filename: "component.tsx",
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
},
{
code: `
export type MyType = string;
`,
filename: "someFile.ts",
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
},
],
invalid: [
{
code: `
export interface MyInterface {
prop: string;
}
`,
filename: "component.tsx",
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
errors: [
{
messageId: "noExportedTypesInTsxFiles",
},
],
},
{
code: `
export type MyType = string;
`,
filename: "component.tsx",
parserOptions: { ecmaVersion: 2020, sourceType: "module" },
errors: [
{
messageId: "noExportedTypesInTsxFiles",
},
],
},
],
});
17 changes: 17 additions & 0 deletions yarn.lock
Original file line number Diff line number Diff line change
Expand Up @@ -304,6 +304,13 @@
resolved "https://registry.yarnpkg.com/@types/json-schema/-/json-schema-7.0.15.tgz#596a1747233694d50f6ad8a7869fcb6f56cf5841"
integrity sha512-5+fP8P8MFNC+AyZCDxrB2pkZFPGzqQWUzpSeuuVLvm8VMcorNYavBqoFcxK8bQz4Qsbn4oUEEem4wDLfcysGHA==

"@types/node@^22.7.9":
version "22.7.9"
resolved "https://registry.yarnpkg.com/@types/node/-/node-22.7.9.tgz#2bf2797b5e84702d8262ea2cf843c3c3c880d0e9"
integrity sha512-jrTfRC7FM6nChvU7X2KqcrgquofrWLFDeYC1hKfwNWomVvrn7JIksqf344WN2X/y8xrgqBd2dJATZV4GbatBfg==
dependencies:
undici-types "~6.19.2"

"@types/semver@^7.5.0", "@types/semver@^7.5.8":
version "7.5.8"
resolved "https://registry.yarnpkg.com/@types/semver/-/semver-7.5.8.tgz#8268a8c57a3e4abd25c165ecd36237db7948a55e"
Expand Down Expand Up @@ -1333,6 +1340,11 @@ prelude-ls@^1.2.1:
resolved "https://registry.yarnpkg.com/prelude-ls/-/prelude-ls-1.2.1.tgz#debc6489d7a6e6b0e7611888cec880337d316396"
integrity sha512-vkcDPrRZo1QZLbn5RLGPpg/WmIQ65qoWWhcGKf/b5eplkkarX0m9z8ppCat4mlOqUsWpyNuYgO3VRyrYHSzX5g==

prettier@^3.3.3:
version "3.3.3"
resolved "https://registry.yarnpkg.com/prettier/-/prettier-3.3.3.tgz#30c54fe0be0d8d12e6ae61dbb10109ea00d53105"
integrity sha512-i2tDNA0O5IrMO757lfrdQZCc2jPNDVntV0m/+4whiDfWaTKfMNgR7Qz0NAeGz/nRqF4m5/6CLzbP4/liHt12Ew==

pretty-format@^29.7.0:
version "29.7.0"
resolved "https://registry.yarnpkg.com/pretty-format/-/pretty-format-29.7.0.tgz#ca42c758310f365bfa71a0bda0a807160b776812"
Expand Down Expand Up @@ -1544,6 +1556,11 @@ ufo@^1.3.2:
resolved "https://registry.yarnpkg.com/ufo/-/ufo-1.5.3.tgz#3325bd3c977b6c6cd3160bf4ff52989adc9d3344"
integrity sha512-Y7HYmWaFwPUmkoQCUIAYpKqkOf+SbVj/2fJJZ4RJMCfZp0rTGwRbzQD+HghfnhKOjL9E01okqz+ncJskGYfBNw==

undici-types@~6.19.2:
version "6.19.8"
resolved "https://registry.yarnpkg.com/undici-types/-/undici-types-6.19.8.tgz#35111c9d1437ab83a7cdc0abae2f26d88eda0a02"
integrity sha512-ve2KP6f/JnbPBFyobGHuerC9g1FYGn/F8n1LWTwNxCEzd6IfqTwUQcNXgEtmmQ6DlRrC1hrSrBnCZPokRrDHjw==

uri-js@^4.2.2:
version "4.4.1"
resolved "https://registry.yarnpkg.com/uri-js/-/uri-js-4.4.1.tgz#9b1a52595225859e55f669d928f88c6c57f2a77e"
Expand Down

0 comments on commit aefc977

Please sign in to comment.