@vijayhardaha/dev-config 2.0.0 → 2.0.1

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
@@ -27,7 +27,7 @@ bun install @vijayhardaha/dev-config --dev
27
27
  ### Install Required Packages
28
28
 
29
29
  ```bash
30
- bun add --dev eslint prettier @prettier/plugin-xml eslint-plugin-prettier globals eslint-plugin-jsdoc eslint-plugin-import-x typescript typescript-eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
30
+ bun add --dev eslint @eslint/compat prettier @prettier/plugin-xml eslint-plugin-prettier globals eslint-plugin-jsdoc eslint-plugin-import-x typescript typescript-eslint @typescript-eslint/eslint-plugin @typescript-eslint/parser
31
31
  ```
32
32
 
33
33
  ### Install Optional Packages
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@vijayhardaha/dev-config",
3
- "version": "2.0.0",
3
+ "version": "2.0.1",
4
4
  "description": "Reusable development configurations for Next.js + TypeScript projects",
5
5
  "scripts": {
6
6
  "lint": "eslint .",
@@ -74,10 +74,8 @@
74
74
  "stylelint-order": "^8.1.1",
75
75
  "vitest": "^4.1.7"
76
76
  },
77
- "dependencies": {
78
- "@eslint/compat": "^2.1.0"
79
- },
80
77
  "peerDependencies": {
78
+ "@eslint/compat": ">=2",
81
79
  "@prettier/plugin-xml": ">=3",
82
80
  "@typescript-eslint/eslint-plugin": ">=8",
83
81
  "@typescript-eslint/parser": ">=8",
@@ -11,12 +11,127 @@ import { commonRules } from './rules.js';
11
11
  import { commonParser } from './setup.js';
12
12
 
13
13
  /**
14
- * Builds a common ESLint configuration with support for various options.
14
+ * Filters and flattens conditional plugins based on user options.
15
+ *
16
+ * @param {object} conditionalPlugins - Plugin map keyed by option name.
17
+ * @param {object} options - User-provided options.
18
+ *
19
+ * @returns {object[]} Flat config objects for enabled plugins.
20
+ */
21
+ const getEnabledPlugins = (conditionalPlugins, options) =>
22
+ Object.entries(conditionalPlugins)
23
+ .filter(([key]) => options[key])
24
+ .flatMap(([, value]) => (Array.isArray(value) ? value : [value]));
25
+
26
+ /**
27
+ * Flattens a mixed list of config arrays and objects into a flat config array.
28
+ *
29
+ * @param {(Array|object)[]} plugins - Mixed list of flat config arrays and objects.
30
+ *
31
+ * @returns {object[]} Flattened config array.
32
+ */
33
+ const flattenPlugins = (plugins) => {
34
+ const result = [];
35
+
36
+ for (const plugin of plugins) {
37
+ if (Array.isArray(plugin)) {
38
+ result.push(...plugin);
39
+ } else if (typeof plugin === 'object' && plugin !== null) {
40
+ result.push(plugin);
41
+ }
42
+ }
43
+
44
+ return result;
45
+ };
46
+
47
+ /**
48
+ * Wraps plugin rules with fixupPluginRules for ESLint 10 backward compatibility.
49
+ *
50
+ * @param {object[]} flatConfigs - Flat config array.
51
+ *
52
+ * @returns {object[]} Config array with wrapped plugin rules.
53
+ */
54
+ const fixPlugins = (flatConfigs) =>
55
+ flatConfigs.map((config) => {
56
+ if (!config.plugins) return config;
57
+
58
+ const fixed = { ...config, plugins: {} };
59
+
60
+ for (const [name, plugin] of Object.entries(config.plugins)) {
61
+ fixed.plugins[name] = fixupPluginRules(plugin);
62
+ }
63
+
64
+ return fixed;
65
+ });
66
+
67
+ /**
68
+ * Merges user-provided global ignores with the common defaults.
69
+ *
70
+ * @param {string[]|undefined} userGlobalIgnores - User-provided global ignore patterns.
71
+ *
72
+ * @returns {object} ESLint ignores config object.
73
+ */
74
+ const mergeGlobalIgnores = (userGlobalIgnores) =>
75
+ Array.isArray(userGlobalIgnores) ? globalIgnores(userGlobalIgnores) : globalIgnores();
76
+
77
+ /**
78
+ * Builds the main ESLint config object with language options, settings, and rules.
79
+ *
80
+ * @param {object} ctx - Context object with all config parameters.
81
+ * @param {string[]} ctx.filePatterns - File patterns to apply the config to.
82
+ * @param {object} ctx.opts - Resolved user options.
83
+ * @param {boolean} ctx.typescript - Enable TypeScript support.
84
+ * @param {object} ctx.extraLanguageOptions - Additional language options.
85
+ * @param {object} ctx.parserOptions - Parser options.
86
+ * @param {object} ctx.extraSettings - Additional settings.
87
+ * @param {object} ctx.extraRules - Additional rules.
88
+ *
89
+ * @returns {import('eslint').Linter.Config} ESLint config object.
90
+ */
91
+ const buildConfigObject = ({
92
+ filePatterns,
93
+ opts,
94
+ typescript,
95
+ extraLanguageOptions,
96
+ parserOptions,
97
+ extraSettings,
98
+ extraRules,
99
+ }) => {
100
+ const { ignores, rules, settings, languageOptions, extend } = opts;
101
+
102
+ return {
103
+ files: [...filePatterns],
104
+ ...(ignores && { ignores }),
105
+ ...(typescript && { plugins: { '@typescript-eslint': tsEslint.plugin } }),
106
+ languageOptions: {
107
+ ...commonLanguageOptions,
108
+ ...(typescript && commonParser),
109
+ ...extraLanguageOptions,
110
+ ...languageOptions,
111
+ ...(typescript && { parserOptions: { tsconfigRootDir: process.cwd(), ...parserOptions } }),
112
+ },
113
+ settings: {
114
+ ...(opts.importOrder && { 'import-x/resolver': { typescript: {} } }),
115
+ ...(opts.jsdoc && { jsdoc: { mode: 'typescript' } }),
116
+ ...extraSettings,
117
+ ...settings,
118
+ },
119
+ rules: {
120
+ ...commonRules({ prettier: opts.prettier, importOrder: opts.importOrder, typescript, jsdoc: opts.jsdoc }),
121
+ ...extraRules,
122
+ ...rules,
123
+ },
124
+ ...extend,
125
+ };
126
+ };
127
+
128
+ /**
129
+ * Builds a flat ESLint configuration with support for various options.
15
130
  *
16
131
  * @param {object} config - Configuration options.
17
132
  * @param {string[]} config.files - File patterns to apply the config to.
18
133
  * @param {(Array|object)[]} config.builtinPlugins - Flat config arrays or objects to always include.
19
- * @param {object} config.conditionalPlugins - Conditional plugins based on options (e.g., { react: true, a11y: true }).
134
+ * @param {object} config.conditionalPlugins - Conditional plugins based on options.
20
135
  * @param {object} [config.languageOptions] - Additional language options.
21
136
  * @param {object} [config.parserOptions] - Parser options.
22
137
  * @param {object} [config.settings] - Settings object.
@@ -37,79 +152,31 @@ export const buildConfig = ({
37
152
  options = {},
38
153
  typescript = false,
39
154
  }) => {
40
- const {
41
- prettier = true,
42
- importOrder = true,
43
- jsdoc = true,
44
- ignores,
45
- rules,
46
- settings,
47
- languageOptions,
48
- plugins: userPlugins,
49
- globalIgnores: userGlobalIgnores,
50
- extend,
51
- } = options;
52
-
53
- // ---- Build extends configs ----
54
- const conditionalPluginList = Object.entries(conditionalPlugins)
55
- .filter(([key]) => options[key])
56
- .flatMap(([, value]) => (Array.isArray(value) ? value : [value]));
155
+ const opts = { prettier: true, importOrder: true, jsdoc: true, ...options };
156
+
157
+ const conditionalPluginList = getEnabledPlugins(conditionalPlugins, opts);
57
158
 
58
- const builtPlugins = [
159
+ const mergedPlugins = [
59
160
  ...builtinPlugins,
60
- importOrder && importX.flatConfigs.recommended,
61
- jsdoc && jsdocPlugin.configs['flat/recommended'],
62
- prettier && prettierRecommended,
161
+ opts.importOrder && importX.flatConfigs.recommended,
162
+ opts.jsdoc && jsdocPlugin.configs['flat/recommended'],
163
+ opts.prettier && prettierRecommended,
63
164
  ...conditionalPluginList,
165
+ ...(opts.plugins || []),
64
166
  ].filter(Boolean);
65
167
 
66
- const plugins = [...builtPlugins, ...(userPlugins || [])];
168
+ const flatConfigs = flattenPlugins(mergedPlugins);
169
+ const fixedConfigs = fixPlugins(flatConfigs);
170
+ const mergedGlobalIgnores = mergeGlobalIgnores(opts.globalIgnores);
67
171
 
68
- // ---- Build config object ----
69
- const configObject = {
70
- files: [...filePatterns],
71
- ...(ignores && { ignores }),
72
- ...(typescript && { plugins: { '@typescript-eslint': tsEslint.plugin } }),
73
- languageOptions: {
74
- ...commonLanguageOptions,
75
- ...(typescript && commonParser),
76
- ...extraLanguageOptions,
77
- ...languageOptions,
78
- ...(typescript && { parserOptions: { tsconfigRootDir: process.cwd(), ...parserOptions } }),
79
- },
80
- settings: {
81
- ...(importOrder && { 'import-x/resolver': { typescript: {} } }),
82
- ...(jsdoc && { jsdoc: { mode: 'typescript' } }),
83
- ...extraSettings,
84
- ...settings,
85
- },
86
- rules: { ...commonRules({ prettier, importOrder, typescript, jsdoc }), ...extraRules, ...rules },
87
- ...extend,
88
- };
89
-
90
- // Merge user global ignores with common global ignores
91
- const mergedGlobalIgnores = Array.isArray(userGlobalIgnores) ? globalIgnores(userGlobalIgnores) : globalIgnores();
92
-
93
- // Collect all flat configs (arrays spread, objects added directly)
94
- const flatConfigs = [];
95
-
96
- for (const plugin of plugins) {
97
- if (Array.isArray(plugin)) {
98
- flatConfigs.push(...plugin);
99
- } else if (typeof plugin === 'object' && plugin !== null) {
100
- flatConfigs.push(plugin);
101
- }
102
- }
103
-
104
- // Wrap plugins with fixupPluginRules for ESLint 10 backward compatibility
105
- // (addresses removed APIs like context.getFilename in eslint-plugin-react)
106
- const fixedConfigs = flatConfigs.map((config) => {
107
- if (!config.plugins) return config;
108
- const fixed = { ...config, plugins: {} };
109
- for (const [name, plugin] of Object.entries(config.plugins)) {
110
- fixed.plugins[name] = fixupPluginRules(plugin);
111
- }
112
- return fixed;
172
+ const configObject = buildConfigObject({
173
+ filePatterns,
174
+ opts,
175
+ typescript,
176
+ extraLanguageOptions,
177
+ parserOptions,
178
+ extraSettings,
179
+ extraRules,
113
180
  });
114
181
 
115
182
  return defineConfig([...mergedGlobalIgnores, ...fixedConfigs, configObject]);
@@ -1,3 +1,72 @@
1
+ // ---- JSDoc Rules: enforce documentation on public/exported APIs ----
2
+
3
+ /**
4
+ * JSDoc rules that enforce documentation presence on public/exported APIs.
5
+ * Covers `@param`, `@returns`, `@throws`, `@description`, and `@property-description`.
6
+ *
7
+ * @type {object}
8
+ */
9
+ const JSDOC_REQUIRE_RULES = {
10
+ 'jsdoc/require-jsdoc': [
11
+ 'error',
12
+ {
13
+ publicOnly: true,
14
+ require: {
15
+ FunctionDeclaration: true,
16
+ MethodDefinition: true,
17
+ ClassDeclaration: true,
18
+ ArrowFunctionExpression: true,
19
+ },
20
+ },
21
+ ],
22
+ 'jsdoc/require-description': 'error',
23
+ 'jsdoc/require-param': 'error',
24
+ 'jsdoc/require-param-name': 'error',
25
+ 'jsdoc/require-param-description': 'error',
26
+ 'jsdoc/require-param-type': 'error',
27
+ 'jsdoc/require-returns': 'error',
28
+ 'jsdoc/require-returns-description': 'error',
29
+ 'jsdoc/require-returns-type': 'error',
30
+ 'jsdoc/require-throws': 'error',
31
+ 'jsdoc/require-property-description': 'warn',
32
+ };
33
+
34
+ /**
35
+ * JSDoc rules that validate tag names, types, and undefined type references.
36
+ *
37
+ * @type {object}
38
+ */
39
+ const JSDOC_CORRECTNESS_RULES = {
40
+ 'jsdoc/check-tag-names': 'error',
41
+ 'jsdoc/no-undefined-types': ['error', { definedTypes: ['JSX.Element'] }],
42
+ 'jsdoc/valid-types': 'error',
43
+ };
44
+
45
+ /**
46
+ * JSDoc rules that enforce consistent formatting and tag ordering.
47
+ *
48
+ * @type {object}
49
+ */
50
+ const JSDOC_STYLE_RULES = {
51
+ 'jsdoc/tag-lines': ['error', 'any', { startLines: 1, endLines: 0, applyToEndTag: true }],
52
+ 'jsdoc/check-alignment': 'error',
53
+ 'jsdoc/check-indentation': 'off',
54
+ 'jsdoc/sort-tags': [
55
+ 'warn',
56
+ {
57
+ tagSequence: [
58
+ { tags: ['description'] },
59
+ { tags: ['template'] },
60
+ { tags: ['param'] },
61
+ { tags: ['returns'] },
62
+ { tags: ['example'] },
63
+ ],
64
+ },
65
+ ],
66
+ 'jsdoc/no-types': 'off',
67
+ 'jsdoc/informative-docs': 'off',
68
+ };
69
+
1
70
  /**
2
71
  * Creates JSDoc rules for enforcing documentation on public/exported APIs.
3
72
  *
@@ -6,72 +75,7 @@
6
75
  * @returns {object} JSDoc ESLint rules object.
7
76
  */
8
77
  const jsdocRules = (jsdoc = true) =>
9
- jsdoc
10
- ? {
11
- // ---- JSDoc Rules for PUBLIC / EXPORTED APIs ----
12
- 'jsdoc/require-jsdoc': [
13
- 'error',
14
- {
15
- publicOnly: true,
16
- require: {
17
- FunctionDeclaration: true,
18
- MethodDefinition: true,
19
- ClassDeclaration: true,
20
- ArrowFunctionExpression: true,
21
- },
22
- },
23
- ],
24
-
25
- // Descriptions must exist and be meaningful
26
- 'jsdoc/require-description': 'error',
27
-
28
- // Params must be fully documented
29
- 'jsdoc/require-param': 'error',
30
- 'jsdoc/require-param-name': 'error',
31
- 'jsdoc/require-param-description': 'error',
32
- 'jsdoc/require-param-type': 'error',
33
-
34
- // Returns must be documented
35
- 'jsdoc/require-returns': 'error',
36
- 'jsdoc/require-returns-description': 'error',
37
- 'jsdoc/require-returns-type': 'error',
38
-
39
- // Throws must be documented
40
- 'jsdoc/require-throws': 'error',
41
-
42
- // Strict correctness
43
- 'jsdoc/check-tag-names': 'error',
44
- 'jsdoc/no-undefined-types': ['error', { definedTypes: ['JSX.Element'] }],
45
- 'jsdoc/valid-types': 'error',
46
-
47
- // Enforce clean structure
48
- 'jsdoc/tag-lines': ['error', 'any', { startLines: 1, endLines: 0, applyToEndTag: true }],
49
-
50
- 'jsdoc/check-alignment': 'error',
51
- 'jsdoc/check-indentation': 'off',
52
-
53
- // Optional but powerful for large teams
54
- 'jsdoc/sort-tags': [
55
- 'warn',
56
- {
57
- tagSequence: [
58
- { tags: ['description'] },
59
- { tags: ['template'] },
60
- { tags: ['param'] },
61
- { tags: ['returns'] },
62
- { tags: ['example'] },
63
- ],
64
- },
65
- ],
66
-
67
- // Avoid useless docs
68
- 'jsdoc/no-types': 'off',
69
- 'jsdoc/informative-docs': 'off',
70
-
71
- // Enforce property docs in typedef-style (optional)
72
- 'jsdoc/require-property-description': 'warn',
73
- }
74
- : {};
78
+ jsdoc ? { ...JSDOC_REQUIRE_RULES, ...JSDOC_CORRECTNESS_RULES, ...JSDOC_STYLE_RULES } : {};
75
79
 
76
80
  /**
77
81
  * Creates TypeScript-specific rules.
@@ -1,71 +1,61 @@
1
- import { describe, it, expect } from 'vitest';
1
+ import { describe, it, expect, beforeAll } from 'vitest';
2
2
 
3
3
  // Test suite for the ESLint rules configuration module.
4
4
  describe('eslint/lib/rules.js', () => {
5
- // Test that the module exports the commonRules function.
6
- it('should export commonRules function', async () => {
7
- // Dynamically import the rules module to test its exports.
5
+ let commonRules;
6
+
7
+ // Module-level import before tests to reduce duplication.
8
+ beforeAll(async () => {
8
9
  const module = await import('./rules.js');
10
+ commonRules = module.commonRules;
11
+ });
9
12
 
13
+ // Test that the module exports the commonRules function.
14
+ it('should export commonRules function', () => {
10
15
  // Verify that commonRules is a function.
11
- expect(typeof module.commonRules).toBe('function');
16
+ expect(typeof commonRules).toBe('function');
12
17
  });
13
18
 
14
19
  // Test that commonRules returns an object containing ESLint rules.
15
- it('should return an object with rules', async () => {
16
- // Dynamically import the rules module to test its function.
17
- const module = await import('./rules.js');
18
-
20
+ it('should return an object with rules', () => {
19
21
  // Call commonRules with no options to get base rules.
20
- const result = module.commonRules();
22
+ const result = commonRules();
21
23
 
22
24
  // Verify that the result is an object (ESLint rules config).
23
25
  expect(typeof result).toBe('object');
24
26
  });
25
27
 
26
28
  // Test that TypeScript rules are included when typescript option is true.
27
- it('should include TypeScript rules when typescript is true', async () => {
28
- // Dynamically import the rules module to test its function.
29
- const module = await import('./rules.js');
30
-
29
+ it('should include TypeScript rules when typescript is true', () => {
31
30
  // Call commonRules with typescript option enabled.
32
- const result = module.commonRules({ typescript: true });
31
+ const result = commonRules({ typescript: true });
33
32
 
34
33
  // Verify that a TypeScript-specific rule is present in the config.
35
34
  expect(result['@typescript-eslint/no-unused-vars']).toBeDefined();
36
35
  });
37
36
 
38
37
  // Test that import order rules are included when importOrder option is true.
39
- it('should include import order rules when importOrder is true', async () => {
40
- // Dynamically import the rules module to test its function.
41
- const module = await import('./rules.js');
42
-
38
+ it('should include import order rules when importOrder is true', () => {
43
39
  // Call commonRules with importOrder option enabled.
44
- const result = module.commonRules({ importOrder: true });
40
+ const result = commonRules({ importOrder: true });
45
41
 
46
42
  // Verify that the import-x/order rule is present in the config.
47
43
  expect(result['import-x/order']).toBeDefined();
48
44
  });
49
45
 
50
46
  // Test that Prettier rules are included when prettier option is true.
51
- it('should include Prettier rules when prettier is true', async () => {
52
- // Dynamically import the rules module to test its function.
53
- const module = await import('./rules.js');
54
-
47
+ it('should include Prettier rules when prettier is true', () => {
55
48
  // Call commonRules with prettier option enabled.
56
- const result = module.commonRules({ prettier: true });
49
+ const result = commonRules({ prettier: true });
57
50
 
58
51
  // Verify that the prettier/prettier rule is present in the config.
59
52
  expect(result['prettier/prettier']).toBeDefined();
60
53
  });
61
54
 
62
55
  // Test that JSDoc rules are included when jsdoc option is true.
63
- it('should include JSDoc rules when jsdoc is true', async () => {
64
- // Dynamically import the rules module to test its function.
65
- const module = await import('./rules.js');
66
-
56
+ it('should include JSDoc rules when jsdoc is true', () => {
67
57
  // Call commonRules with jsdoc option enabled.
68
- const result = module.commonRules({ jsdoc: true });
58
+ const result = commonRules({ jsdoc: true });
69
59
 
70
60
  // Verify that the jsdoc/require-jsdoc rule is present in the config.
71
61
  expect(result['jsdoc/require-jsdoc']).toBeDefined();