@vp-tw/eslint-config 0.0.1 → 0.0.2

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
package/README.md CHANGED
@@ -3,9 +3,13 @@
3
3
  ViPro's ESLint configuration.
4
4
 
5
5
  - Built on top of [antfu/eslint-config](https://github.com/antfu/eslint-config).
6
- - Uses [Prettier](https://github.com/prettier/prettier) for code formatting.
7
- - Integrates with [eslint-mdx](https://github.com/mdx-js/eslint-mdx).
8
- - Fine-tuned to provide an opinionated, optimal DX.
6
+ - Integrates with:
7
+ - [eslint-config-prettier](https://github.com/prettier/eslint-config-prettier)
8
+ - [eslint-mdx](https://github.com/mdx-js/eslint-mdx).
9
+ - [eslint-plugin-storybook](https://github.com/storybookjs/eslint-plugin-storybook)
10
+ - Fine-tuned to provide an opinionated, optimal DX. Please copy the VSCode settings from [eslint-config.code-workspace](https://github.com/VdustR/eslint-config/blob/main/eslint-config.code-workspace).
11
+
12
+ Click [here](https://vdustr.dev/eslint-config) to preview the ESLint inspection.
9
13
 
10
14
  ## License
11
15
 
package/package.json CHANGED
@@ -1,7 +1,12 @@
1
1
  {
2
2
  "name": "@vp-tw/eslint-config",
3
- "version": "0.0.1",
3
+ "version": "0.0.2",
4
4
  "description": "ViPro's ESLint configuration",
5
+ "homepage": "https://github.com/vdustr/eslint-config",
6
+ "author": {
7
+ "name": "ViPro",
8
+ "url": "https://vdustr.dev"
9
+ },
5
10
  "exports": {
6
11
  ".": {
7
12
  "import": {
@@ -41,8 +46,9 @@
41
46
  "eslint-flat-config-utils": "^2.0.0",
42
47
  "eslint-plugin-mdx": "^3.1.5",
43
48
  "eslint-plugin-package-json": "^0.21.1",
44
- "eslint-plugin-react": "^7.37.4",
45
- "eslint-plugin-react-compiler": "^19.0.0-beta-27714ef-20250124"
49
+ "eslint-plugin-react-compiler": "^19.0.0-beta-27714ef-20250124",
50
+ "eslint-plugin-storybook": "^0.11.2",
51
+ "local-pkg": "^1.0.0"
46
52
  },
47
53
  "devDependencies": {
48
54
  "eslint-typegen": "^1.0.0",
@@ -53,9 +59,7 @@
53
59
  },
54
60
  "scripts": {
55
61
  "build": "unbuild",
56
- "copy": "run-p copy:*",
57
- "copy:license": "fse copy ../../LICENSE LICENSE",
58
- "copy:readme": "fse copy ../../README.md README.md",
62
+ "copy": "tsx scripts/copy",
59
63
  "typegen": "tsx scripts/typegen.ts"
60
64
  }
61
65
  }
@@ -0,0 +1,23 @@
1
+ import fs from "fs-extra";
2
+ import path from "pathe";
3
+ import { packageDirectory } from "pkg-dir";
4
+
5
+ const filesToCopy = ["README.md", "LICENSE"] satisfies Array<string>;
6
+
7
+ (async () => {
8
+ const pkgDir = await packageDirectory();
9
+ if (!pkgDir) {
10
+ throw new Error("Could not find package directory");
11
+ }
12
+ const rootPkgDir = await packageDirectory({
13
+ cwd: path.resolve(pkgDir, ".."),
14
+ });
15
+
16
+ if (!rootPkgDir) {
17
+ throw new Error("Could not find root package directory");
18
+ }
19
+
20
+ for (const file of filesToCopy) {
21
+ await fs.copyFile(path.join(rootPkgDir, file), path.join(pkgDir, file));
22
+ }
23
+ })();
@@ -1,6 +1,7 @@
1
1
  import type { ESLint } from "eslint";
2
- import { rules } from "eslint-plugin-mdx/lib/rules";
2
+ import { rules as mdxRules } from "eslint-plugin-mdx/lib/rules";
3
3
  import reactCompiler from "eslint-plugin-react-compiler";
4
+ import storybook from "eslint-plugin-storybook";
4
5
  import { pluginsToRulesDTS } from "eslint-typegen/core";
5
6
  import fs from "fs-extra";
6
7
  import path from "pathe";
@@ -25,9 +26,18 @@ import { packageDirectory } from "pkg-dir";
25
26
  ...definePlugins({ "react-compiler": reactCompiler }),
26
27
  ...definePlugins({
27
28
  mdx: {
28
- rules,
29
+ rules: mdxRules,
29
30
  },
30
31
  }),
32
+ ...definePlugins({
33
+ ...Object.fromEntries(
34
+ storybook.configs["flat/csf-strict"].flatMap((config) =>
35
+ !("plugins" in config) || !config.plugins
36
+ ? []
37
+ : Object.entries(config.plugins),
38
+ ),
39
+ ),
40
+ }),
31
41
  };
32
42
 
33
43
  await fs.writeFile(targetPath, await pluginsToRulesDTS(plugins));
@@ -1,14 +1,15 @@
1
1
  import type { TypedFlatConfigItem } from "@antfu/eslint-config";
2
2
  import type { DistributiveOmit } from "@mui/types";
3
- import type { Config } from "../types";
3
+ import { defaultPluginRenaming, renameRules } from "@antfu/eslint-config";
4
4
 
5
+ // eslint-disable-next-line import/no-namespace -- `mdx` exports the plugin as a namespace.
5
6
  import * as mdxPlugin from "eslint-plugin-mdx";
6
7
 
7
8
  namespace mdx {
8
9
  export interface Options
9
10
  extends DistributiveOmit<TypedFlatConfigItem, "processor"> {
10
11
  processorOptions?: mdxPlugin.ProcessorOptions;
11
- flatCodeBlocks?: boolean | Config;
12
+ flatCodeBlocks?: boolean | TypedFlatConfigItem;
12
13
  }
13
14
  }
14
15
 
@@ -16,12 +17,23 @@ const mdx = ({
16
17
  flatCodeBlocks = true,
17
18
  processorOptions,
18
19
  ...options
19
- }: mdx.Options = {}) =>
20
- [
20
+ }: mdx.Options = {}) => {
21
+ const flatCodeBlocksOptions: TypedFlatConfigItem =
22
+ typeof flatCodeBlocks !== "object" ? {} : flatCodeBlocks;
23
+ return [
21
24
  {
22
25
  name: "vdustr/mdx",
23
26
  ...mdxPlugin.flat,
24
27
  ...options,
28
+ rules: {
29
+ ...renameRules(
30
+ {
31
+ ...mdxPlugin.flat.rules,
32
+ },
33
+ defaultPluginRenaming,
34
+ ),
35
+ ...options.rules,
36
+ },
25
37
  processor: mdxPlugin.createRemarkProcessor({
26
38
  lintCodeBlocks: Boolean(flatCodeBlocks),
27
39
  ...processorOptions,
@@ -36,9 +48,19 @@ const mdx = ({
36
48
  {
37
49
  name: "vdustr/mdx/flat-code-blocks",
38
50
  ...mdxPlugin.flatCodeBlocks,
39
- ...(typeof flatCodeBlocks !== "object" ? null : flatCodeBlocks),
40
- } satisfies Config,
51
+ ...flatCodeBlocksOptions,
52
+ rules: {
53
+ ...renameRules(
54
+ {
55
+ ...mdxPlugin.flatCodeBlocks.rules,
56
+ },
57
+ defaultPluginRenaming,
58
+ ),
59
+ ...flatCodeBlocksOptions.rules,
60
+ },
61
+ } satisfies TypedFlatConfigItem,
41
62
  ]),
42
- ] satisfies Array<Config>;
63
+ ] satisfies Array<TypedFlatConfigItem>;
64
+ };
43
65
 
44
66
  export { mdx };
@@ -1,12 +1,12 @@
1
- import type { Config } from "../types";
2
- import { defaultPluginRenaming, renameRules } from "@antfu/eslint-config";
1
+ import type { TypedFlatConfigItem } from "@antfu/eslint-config";
3
2
 
4
3
  import eslintConfigPrettier from "eslint-config-prettier";
4
+ import { renameRules } from "../utils/renameRules";
5
5
 
6
- const prettier: Config = {
6
+ const prettier = {
7
7
  name: "vdustr/prettier",
8
8
  ...eslintConfigPrettier,
9
- rules: renameRules(eslintConfigPrettier.rules, defaultPluginRenaming),
10
- };
9
+ rules: renameRules(eslintConfigPrettier.rules),
10
+ } satisfies TypedFlatConfigItem;
11
11
 
12
12
  export { prettier };
@@ -0,0 +1,20 @@
1
+ import type { TypedFlatConfigItem } from "@antfu/eslint-config";
2
+ import storybookPlugin from "eslint-plugin-storybook";
3
+ import { renameRules } from "../utils/renameRules";
4
+
5
+ const storybook = storybookPlugin.configs["flat/csf-strict"].map((config) => {
6
+ const allConfig = config as TypedFlatConfigItem;
7
+ const { rules, files, plugins, ...rest } = allConfig;
8
+ return {
9
+ ...rest,
10
+ ...(!rules
11
+ ? null
12
+ : {
13
+ rules: renameRules(rules),
14
+ }),
15
+ ...(!files ? null : { files }),
16
+ ...(!plugins ? null : { plugins }),
17
+ } satisfies TypedFlatConfigItem;
18
+ });
19
+
20
+ export { storybook };
@@ -17,10 +17,90 @@ export interface RuleOptions {
17
17
  * Surfaces diagnostics from React Forget
18
18
  */
19
19
  'react-compiler/react-compiler'?: Linter.RuleEntry<ReactCompilerReactCompiler>
20
+ /**
21
+ * Interactions should be awaited
22
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/await-interactions.md
23
+ */
24
+ 'storybook/await-interactions'?: Linter.RuleEntry<[]>
25
+ /**
26
+ * Pass a context when invoking play function of another story
27
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/context-in-play-function.md
28
+ */
29
+ 'storybook/context-in-play-function'?: Linter.RuleEntry<[]>
30
+ /**
31
+ * The component property should be set
32
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/csf-component.md
33
+ */
34
+ 'storybook/csf-component'?: Linter.RuleEntry<[]>
35
+ /**
36
+ * Story files should have a default export
37
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/default-exports.md
38
+ */
39
+ 'storybook/default-exports'?: Linter.RuleEntry<[]>
40
+ /**
41
+ * Deprecated hierarchy separator in title property
42
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/hierarchy-separator.md
43
+ */
44
+ 'storybook/hierarchy-separator'?: Linter.RuleEntry<[]>
45
+ /**
46
+ * Meta should only have inline properties
47
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/meta-inline-properties.md
48
+ */
49
+ 'storybook/meta-inline-properties'?: Linter.RuleEntry<StorybookMetaInlineProperties>
50
+ /**
51
+ * A story should not have a redundant name property
52
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/no-redundant-story-name.md
53
+ */
54
+ 'storybook/no-redundant-story-name'?: Linter.RuleEntry<[]>
55
+ /**
56
+ * storiesOf is deprecated and should not be used
57
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/no-stories-of.md
58
+ */
59
+ 'storybook/no-stories-of'?: Linter.RuleEntry<[]>
60
+ /**
61
+ * Do not define a title in meta
62
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/no-title-property-in-meta.md
63
+ */
64
+ 'storybook/no-title-property-in-meta'?: Linter.RuleEntry<[]>
65
+ /**
66
+ * This rule identifies storybook addons that are invalid because they are either not installed or contain a typo in their name.
67
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/no-uninstalled-addons.md
68
+ */
69
+ 'storybook/no-uninstalled-addons'?: Linter.RuleEntry<StorybookNoUninstalledAddons>
70
+ /**
71
+ * Stories should use PascalCase
72
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/prefer-pascal-case.md
73
+ */
74
+ 'storybook/prefer-pascal-case'?: Linter.RuleEntry<[]>
75
+ /**
76
+ * A story file must contain at least one story export
77
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/story-exports.md
78
+ */
79
+ 'storybook/story-exports'?: Linter.RuleEntry<[]>
80
+ /**
81
+ * Use expect from `@storybook/test` or `@storybook/jest`
82
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/use-storybook-expect.md
83
+ */
84
+ 'storybook/use-storybook-expect'?: Linter.RuleEntry<[]>
85
+ /**
86
+ * Do not use testing-library directly on stories
87
+ * @see https://github.com/storybookjs/eslint-plugin-storybook/blob/main/docs/rules/use-storybook-testing-library.md
88
+ */
89
+ 'storybook/use-storybook-testing-library'?: Linter.RuleEntry<[]>
20
90
  }
21
91
 
22
92
  /* ======= Declarations ======= */
23
93
  // ----- react-compiler/react-compiler -----
24
94
  type ReactCompilerReactCompiler = []|[{
25
95
  [k: string]: unknown | undefined
96
+ }]
97
+ // ----- storybook/meta-inline-properties -----
98
+ type StorybookMetaInlineProperties = []|[{
99
+ csfVersion?: number
100
+ }]
101
+ // ----- storybook/no-uninstalled-addons -----
102
+ type StorybookNoUninstalledAddons = []|[{
103
+ packageJsonLocation?: string
104
+ ignore?: string[]
105
+ [k: string]: unknown | undefined
26
106
  }]
package/src/tsconfig.json CHANGED
@@ -1,4 +1,3 @@
1
1
  {
2
- "extends": ["@vp-tw/tsconfig/base/bundler/tsconfig"],
3
- "include": [".", "../lib"]
2
+ "extends": ["@vp-tw/tsconfig/base/bundler/tsconfig"]
4
3
  }
@@ -0,0 +1,7 @@
1
+ import { defaultPluginRenaming, renameRules } from "@antfu/eslint-config";
2
+
3
+ function renameRulesInternal(rules: Parameters<typeof renameRules>[0]) {
4
+ return renameRules(rules, defaultPluginRenaming);
5
+ }
6
+
7
+ export { renameRulesInternal as renameRules };
package/src/vdustr.ts CHANGED
@@ -2,18 +2,25 @@ import type { Config } from "./types";
2
2
  import antfu from "@antfu/eslint-config";
3
3
  import packageJson from "eslint-plugin-package-json/configs/recommended";
4
4
 
5
+ // eslint-disable-next-line import/no-namespace -- This is a cjs module.
5
6
  import * as reactCompilerPlugin from "eslint-plugin-react-compiler";
7
+ import { isPackageExists } from "local-pkg";
6
8
  import { mdx } from "./configs/mdx";
7
9
  import { prettier } from "./configs/prettier";
10
+ import { storybook } from "./configs/storybook";
8
11
  import "./eslint-typegen.d";
9
12
 
10
13
  type Options = NonNullable<Parameters<typeof antfu>[0]> & {
11
14
  mdx?: boolean | mdx.Options;
15
+ storybook?: boolean;
12
16
  };
13
17
 
14
18
  const vdustr = (options?: Options, ...userConfigs: Array<Config>) => {
15
- const mdxOptions = options?.mdx ?? true;
19
+ const typescriptEnabled = isPackageExists("typescript");
16
20
  const jsoncEnabled: boolean = Boolean(options?.jsonc ?? true);
21
+ const reactEnabled: boolean = Boolean(options?.react ?? false);
22
+ const storybookEnabled: boolean = Boolean(options?.storybook ?? false);
23
+ const mdxOptions = options?.mdx ?? true;
17
24
  type Config = NonNullable<Parameters<typeof antfu>[1]>;
18
25
  const defaultConfigs: Array<Config> = [
19
26
  ...(!jsoncEnabled ? [] : [packageJson]),
@@ -22,9 +29,10 @@ const vdustr = (options?: Options, ...userConfigs: Array<Config>) => {
22
29
  : mdx({
23
30
  ...(typeof mdxOptions !== "object" ? null : mdxOptions),
24
31
  })),
32
+ ...(!storybookEnabled ? [] : storybook),
25
33
  prettier,
26
34
  ];
27
- return antfu(
35
+ let config = antfu(
28
36
  {
29
37
  // We use `prettier`.
30
38
  stylistic: false,
@@ -32,7 +40,9 @@ const vdustr = (options?: Options, ...userConfigs: Array<Config>) => {
32
40
  },
33
41
  ...defaultConfigs,
34
42
  ...userConfigs,
35
- )
43
+ );
44
+
45
+ config = config
36
46
  .override("antfu/javascript/rules", (config) => ({
37
47
  ...config,
38
48
  rules: {
@@ -61,9 +71,40 @@ const vdustr = (options?: Options, ...userConfigs: Array<Config>) => {
61
71
  * Forbid `import {} from "module"`.
62
72
  */
63
73
  "import/no-empty-named-blocks": "error",
74
+
75
+ /**
76
+ * Wildcard imports can prevent tree shaking and cause name conflicts.
77
+ * Consider using named imports instead.
78
+ */
79
+ "import/no-namespace": "error",
80
+
81
+ /**
82
+ * Enforcing named exports improves consistency, enhances auto-completion and refactoring, and avoids issues
83
+ * with default export renaming. Additionally, default exports might lead to different behavior when transformed
84
+ * to CJS.
85
+ */
86
+ "import/no-default-export": "error",
64
87
  },
65
88
  }))
66
- .override("antfu/typescript/rules", (config) => ({
89
+
90
+ /**
91
+ * Allow default exports in certain files.
92
+ */
93
+ .insertAfter("antfu/imports/rules", {
94
+ name: "vdustr/allow-default-export",
95
+ rules: {
96
+ "import/no-default-export": "off",
97
+ },
98
+ files: [
99
+ "**/*.config.*",
100
+ // Single file components
101
+ "**/*.vue",
102
+ "**/*.svelte",
103
+ ],
104
+ });
105
+
106
+ if (typescriptEnabled) {
107
+ config = config.override("antfu/typescript/rules", (config) => ({
67
108
  ...config,
68
109
  rules: {
69
110
  ...config.rules,
@@ -84,25 +125,32 @@ const vdustr = (options?: Options, ...userConfigs: Array<Config>) => {
84
125
  */
85
126
  "ts/no-unused-vars": "off",
86
127
  },
87
- }))
88
- .override("antfu/jsonc/rules", (config) => ({
89
- ...config,
90
- rules: {
91
- ...config.rules,
92
- "jsonc/sort-keys": "error",
93
- },
94
- files: [...(config.files ?? []), "**/*.code-workspace"],
95
- ignores: [
96
- ...(config.ignores ?? []),
97
- /**
98
- * Lint `package.json` files with `eslint-plugin-package-json` instead.
99
- */
100
- "**/package.json",
101
- ],
102
- }))
103
- .remove("antfu/sort/package-json")
104
- .remove("antfu/sort/tsconfig-json")
105
- .override("antfu/react/rules", (config) => ({
128
+ }));
129
+ }
130
+
131
+ if (jsoncEnabled) {
132
+ config = config
133
+ .override("antfu/jsonc/rules", (config) => ({
134
+ ...config,
135
+ rules: {
136
+ ...config.rules,
137
+ "jsonc/sort-keys": "error",
138
+ },
139
+ files: [...(config.files ?? []), "**/*.code-workspace"],
140
+ ignores: [
141
+ ...(config.ignores ?? []),
142
+ /**
143
+ * Lint `package.json` files with `eslint-plugin-package-json` instead.
144
+ */
145
+ "**/package.json",
146
+ ],
147
+ }))
148
+ .remove("antfu/sort/package-json")
149
+ .remove("antfu/sort/tsconfig-json");
150
+ }
151
+
152
+ if (reactEnabled) {
153
+ config = config.override("antfu/react/rules", (config) => ({
106
154
  ...config,
107
155
  plugins: {
108
156
  ...config.plugins,
@@ -118,6 +166,19 @@ const vdustr = (options?: Options, ...userConfigs: Array<Config>) => {
118
166
  "react/prefer-destructuring-assignment": "off",
119
167
  },
120
168
  }));
169
+ }
170
+
171
+ if (storybookEnabled) {
172
+ config = config.override("storybook:csf:stories-rules", (config) => ({
173
+ ...config,
174
+ rules: {
175
+ ...config.rules,
176
+ "import/no-default-export": "off",
177
+ },
178
+ }));
179
+ }
180
+
181
+ return config;
121
182
  };
122
183
 
123
184
  export { vdustr };
package/tsconfig.json CHANGED
@@ -1,4 +1,4 @@
1
1
  {
2
- "exclude": ["dist", "src", "node_modules"],
2
+ "exclude": ["configs", "./*.ts", "src/lib"],
3
3
  "extends": ["@vp-tw/tsconfig/base/bundler/tsconfig"]
4
4
  }