zeno-config 0.1.0

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.
@@ -0,0 +1,373 @@
1
+ import { defineConfig } from 'eslint/config';
2
+ import js from '@eslint/js';
3
+ import globals from 'globals';
4
+ import importX from 'eslint-plugin-import-x';
5
+ import reactPlugin from 'eslint-plugin-react';
6
+ import reactHooksPlugin from 'eslint-plugin-react-hooks';
7
+ import prettierPlugin from 'eslint-plugin-prettier/recommended';
8
+ import stylisticPlugin from '@stylistic/eslint-plugin';
9
+ import nodePlugin from 'eslint-plugin-n';
10
+ import reactYouMightNotNeedAnEffectPlugin from 'eslint-plugin-react-you-might-not-need-an-effect';
11
+ import jsxA11yPlugin from 'eslint-plugin-jsx-a11y';
12
+ import unicornPlugin from 'eslint-plugin-unicorn';
13
+ import typescriptEslint from 'typescript-eslint';
14
+ import getBaseRules from './rules/baseRules.js';
15
+ import getImportPluginRules from './rules/importPluginRules.js';
16
+ import getReactPluginRules from './rules/reactPluginRules.js';
17
+ import getReactHooksPluginRules from './rules/reactHooksPluginRules.js';
18
+ import getStylisticPluginRules from './rules/stylisticPluginRules.js';
19
+ import getNodePluginRules from './rules/nodePluginRules.js';
20
+ import getReactYouMightNotNeedAnEffectPluginRules from './rules/reactYouMightNotNeedAnEffectPluginRules.js';
21
+ import getJsxA11yPluginRules from './rules/jsxA11yPluginRules.js';
22
+ import getUnicornPluginRules from './rules/unicornPluginRules.js';
23
+ import getTypescriptPluginRules from './rules/typescriptPluginRules.js';
24
+ import {
25
+ allExtensions,
26
+ allExtensionsString,
27
+ nodeExtensions,
28
+ nodeExtensionsString,
29
+ reactJsExtensions,
30
+ reactJsExtensionsString,
31
+ reactJsExtensionsExtended,
32
+ reactJsExtensionsExtendedString,
33
+ reactExtensions,
34
+ reactExtensionsString,
35
+ reactExtensionsExtended,
36
+ reactExtensionsExtendedString,
37
+ typescriptExtensions,
38
+ typescriptExtensionsString,
39
+ } from '../extensions.js';
40
+
41
+ const ignoreDirs = [
42
+ '**/node_modules/*',
43
+ '**/dist/*',
44
+ '**/build/*',
45
+ '**/coverage/*',
46
+ ];
47
+
48
+ /**
49
+ * Creates the base ESLint configuration.
50
+ *
51
+ * @param {Object} [options={}] - Configuration options.
52
+ * @param {string[]} [options.ignoreExports] - Export patterns to ignore for import rules.
53
+ * @param {Object} [options.extensionsIgnorePattern] - Extension patterns to ignore for import rules.
54
+ * @param {string} [options.webpackConfig] - Path to webpack config for import resolver.
55
+ * @returns {Array} ESLint flat config array.
56
+ */
57
+ const baseConfig = (options = {}) => {
58
+ return [
59
+ {
60
+ name: 'zeno/base',
61
+ files: [`**/*{${allExtensionsString}}`],
62
+ ignores: [...ignoreDirs],
63
+ languageOptions: {
64
+ ecmaVersion: 'latest',
65
+ sourceType: 'module',
66
+ globals: {
67
+ ...globals.node,
68
+ },
69
+ },
70
+ settings: {
71
+ 'import-x/resolver': {
72
+ node: { extensions: allExtensions },
73
+ ...(options.webpackConfig
74
+ ? { webpack: { config: options.webpackConfig } }
75
+ : {}),
76
+ },
77
+ },
78
+ plugins: {
79
+ 'import-x': importX,
80
+ '@stylistic': stylisticPlugin,
81
+ unicorn: unicornPlugin,
82
+ },
83
+ rules: {
84
+ ...getBaseRules(),
85
+ ...getImportPluginRules({
86
+ ignoreExports: options.ignoreExports,
87
+ extensionsIgnorePattern: options.extensionsIgnorePattern,
88
+ }),
89
+ ...getStylisticPluginRules(),
90
+ ...getUnicornPluginRules(),
91
+ },
92
+ extends: [
93
+ // if a new rule is added it'll use the recommended setting until it's added to rules files
94
+ js.configs.recommended,
95
+ ],
96
+ },
97
+ ];
98
+ };
99
+
100
+ /**
101
+ * Creates the Node.js-specific ESLint configuration.
102
+ *
103
+ * @param {Object} [options={}] - Configuration options.
104
+ * @param {string[]} [options.ignoreDirs] - Additional directories to ignore for Node-specific rules.
105
+ * @returns {Array} ESLint flat config array.
106
+ */
107
+ const nodeConfig = (options = {}) => {
108
+ let ignores = [...ignoreDirs];
109
+ if (options.ignoreDirs && options.ignoreDirs.length > 0) {
110
+ const optionsIgnoreDirs = options.ignoreDirs.map((dir) => {
111
+ return `${dir}/**/*{${nodeExtensionsString}}`;
112
+ });
113
+ ignores = [...ignores, ...optionsIgnoreDirs];
114
+ }
115
+ return [
116
+ {
117
+ name: 'zeno/node',
118
+ files: [`**/*{${nodeExtensionsString}}`],
119
+ ignores,
120
+ languageOptions: {
121
+ ecmaVersion: 'latest',
122
+ sourceType: 'module',
123
+ globals: {
124
+ ...globals.node,
125
+ },
126
+ },
127
+ plugins: {
128
+ n: nodePlugin,
129
+ },
130
+ rules: { ...getNodePluginRules() },
131
+ },
132
+ ];
133
+ };
134
+
135
+ /**
136
+ * Creates the React-specific ESLint configuration.
137
+ *
138
+ * @param {Object} [options={}] - Configuration options.
139
+ * @param {string[]} [options.reactDirs] - Directories containing React files (for projects using .js for both React and Node).
140
+ * @param {Object} [options.extensionsIgnorePattern] - Extension patterns to ignore for import rules.
141
+ * @returns {Array} ESLint flat config array.
142
+ */
143
+ const reactConfig = (options = {}) => {
144
+ let files = [`**/*{${reactExtensionsString}}`];
145
+ let extensions = reactExtensions;
146
+
147
+ if (
148
+ options.reactDirs &&
149
+ Array.isArray(options.reactDirs) &&
150
+ options.reactDirs.length > 0
151
+ ) {
152
+ files = options.reactDirs.map((dir) => {
153
+ return `${dir}/**/*{${reactExtensionsExtendedString}}`;
154
+ });
155
+ extensions = reactExtensionsExtended;
156
+ }
157
+
158
+ return [
159
+ {
160
+ name: 'zeno/react',
161
+ files,
162
+ ignores: [...ignoreDirs],
163
+ languageOptions: {
164
+ globals: {
165
+ ...globals.browser,
166
+ },
167
+ parserOptions: {
168
+ ecmaFeatures: {
169
+ jsx: true,
170
+ },
171
+ },
172
+ },
173
+ settings: {
174
+ react: {
175
+ version: 'detect',
176
+ },
177
+ },
178
+ rules: {
179
+ ...getReactPluginRules({ extensions }),
180
+ ...getReactHooksPluginRules(),
181
+ ...getReactYouMightNotNeedAnEffectPluginRules(),
182
+ ...getJsxA11yPluginRules(),
183
+
184
+ 'import-x/extensions': [
185
+ 'error',
186
+ 'ignorePackages',
187
+ {
188
+ js: 'never',
189
+ jsx: 'never',
190
+ ts: 'never',
191
+ tsx: 'never',
192
+ ...options.extensionsIgnorePattern,
193
+ },
194
+ ],
195
+ },
196
+ extends: [
197
+ // if a new rule is added it'll use the recommended setting until it's added to the rules files
198
+ reactPlugin.configs.flat.recommended,
199
+ reactPlugin.configs.flat['jsx-runtime'],
200
+ reactHooksPlugin.configs.flat.recommended,
201
+ reactYouMightNotNeedAnEffectPlugin.configs.recommended,
202
+ jsxA11yPlugin.flatConfigs.recommended,
203
+ ],
204
+ },
205
+ ];
206
+ };
207
+
208
+ /**
209
+ * Creates the TypeScript-specific ESLint configuration.
210
+ *
211
+ * @returns {Array} ESLint flat config array.
212
+ */
213
+ const typescriptConfig = () => {
214
+ return [
215
+ {
216
+ name: 'zeno/typescript',
217
+ files: [`**/*{${typescriptExtensionsString}}`],
218
+ languageOptions: {
219
+ parser: typescriptEslint.parser,
220
+ // parserOptions: {
221
+ // projectService: true,
222
+ // },
223
+ },
224
+ plugins: {
225
+ '@typescript-eslint': typescriptEslint.plugin,
226
+ },
227
+ rules: {
228
+ ...getTypescriptPluginRules(),
229
+
230
+ // other rules the ts compiler handles
231
+ 'react/prop-types': 'off',
232
+ },
233
+ },
234
+ ];
235
+ };
236
+
237
+ const configs = {
238
+ getBase: baseConfig,
239
+ getReact: reactConfig,
240
+ getNode: nodeConfig,
241
+ getTypescript: typescriptConfig,
242
+ };
243
+
244
+ const internals = {
245
+ configs,
246
+ extensions: {
247
+ allExtensions,
248
+ allExtensionsString,
249
+ nodeExtensions,
250
+ nodeExtensionsString,
251
+ reactJsExtensions,
252
+ reactJsExtensionsString,
253
+ reactJsExtensionsExtended,
254
+ reactJsExtensionsExtendedString,
255
+ reactExtensions,
256
+ reactExtensionsString,
257
+ reactExtensionsExtended,
258
+ reactExtensionsExtendedString,
259
+ typescriptExtensions,
260
+ typescriptExtensionsString,
261
+ },
262
+ rules: {
263
+ getBaseRules,
264
+ getImportPluginRules,
265
+ getStylisticPluginRules,
266
+ getUnicornPluginRules,
267
+ getReactPluginRules,
268
+ getReactHooksPluginRules,
269
+ getReactYouMightNotNeedAnEffectPluginRules,
270
+ getJsxA11yPluginRules,
271
+ },
272
+ };
273
+
274
+ // TODO: make sure to add a suggestion to set the engines fields in package.json
275
+
276
+ /**
277
+ * Defines a Zeno ESLint configuration.
278
+ *
279
+ * @param {Object|Array} arg1 - Options object or additional config array. If an array, treated as additional config.
280
+ * @param {boolean} [arg1.react=false] - Enable React-specific rules.
281
+ * @param {boolean} [arg1.ts=true] - Enable TypeScript-specific rules.
282
+ * @param {string[]} [arg1.reactDirs=[]] - Directories containing React files (for projects using .js for both React and Node).
283
+ * @param {string[]} [arg1.nodeIgnoreDirs=[]] - Directories to ignore for Node-specific rules.
284
+ * @param {string[]} [arg1.ignoreExports=[]] - Export patterns to ignore for import rules.
285
+ * @param {Object} [arg1.extensionsIgnorePattern={}] - Extension patterns to ignore for import rules.
286
+ * @param {string} [arg1.webpackConfig] - Path to webpack config for import resolver.
287
+ * @param {Array} [arg2] - Additional ESLint config objects to merge (only used if arg1 is options object).
288
+ * @returns {Array} ESLint flat config array.
289
+ *
290
+ * @example
291
+ * // With options object
292
+ * defineZenoConfig({ react: true, ts: true })
293
+ *
294
+ * @example
295
+ * // With additional config
296
+ * defineZenoConfig({ react: true }, [customConfig])
297
+ *
298
+ * @example
299
+ * // With config array only
300
+ * defineZenoConfig([customConfig])
301
+ */
302
+ const defineZenoConfig = (arg1, arg2) => {
303
+ let options = {
304
+ react: false,
305
+ ts: false,
306
+
307
+ // if a project uses .js file extension for both react and node files this will help separate the rules for each
308
+ reactDirs: [],
309
+ nodeIgnoreDirs: [],
310
+
311
+ ignoreExports: [],
312
+ extensionsIgnorePattern: {},
313
+ webpackConfig: undefined,
314
+ };
315
+ let config;
316
+
317
+ if (Array.isArray(arg1)) {
318
+ config = arg1;
319
+ } else {
320
+ options = arg1 ? { ...options, ...arg1 } : options;
321
+ config = arg2 || [];
322
+ }
323
+
324
+ // Ensure array options are arrays
325
+ if (!Array.isArray(options.reactDirs)) {
326
+ options.reactDirs = [];
327
+ }
328
+ if (!Array.isArray(options.nodeIgnoreDirs)) {
329
+ options.nodeIgnoreDirs = [];
330
+ }
331
+ if (!Array.isArray(options.ignoreExports)) {
332
+ options.ignoreExports = [];
333
+ }
334
+ if (
335
+ typeof options.extensionsIgnorePattern !== 'object' ||
336
+ options.extensionsIgnorePattern === null
337
+ ) {
338
+ options.extensionsIgnorePattern = {};
339
+ }
340
+
341
+ // use the reactDirs as the nodeIgnoreDirs if they are not set since they would likely be the same
342
+ if (
343
+ options.react &&
344
+ options.reactDirs.length > 0 &&
345
+ options.nodeIgnoreDirs.length === 0
346
+ ) {
347
+ options.nodeIgnoreDirs = [...options.reactDirs];
348
+ }
349
+
350
+ return defineConfig([
351
+ ...configs.getBase({
352
+ ignoreExports: options.ignoreExports,
353
+ webpackConfig: options.webpackConfig,
354
+ extensionsIgnorePattern: options.extensionsIgnorePattern,
355
+ }),
356
+ ...configs.getNode({ ignoreDirs: options.nodeIgnoreDirs }),
357
+ ...(options.react
358
+ ? configs.getReact({
359
+ reactDirs: options.reactDirs,
360
+ extensionsIgnorePattern: options.extensionsIgnorePattern,
361
+ })
362
+ : []),
363
+ ...(options.ts ? configs.getTypescript() : []),
364
+
365
+ ...(config || []),
366
+
367
+ // Turn off formatting rules that might conflict with Prettier
368
+ prettierPlugin,
369
+ ]);
370
+ };
371
+
372
+ export default internals;
373
+ export { defineZenoConfig };