@chanyuxi/eslint-waltz 1.3.0 → 1.3.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
@@ -1,6 +1,6 @@
1
1
  # @chanyuxi/eslint-waltz
2
2
 
3
- Composable ESLint flat config for JavaScript, TypeScript, JSONC, React, Tailwind CSS, Perfectionist, and Vitest.
3
+ Composable ESLint flat config for JavaScript, TypeScript, JSONC, React, Tailwind CSS, Perfectionist, and Vitest.
4
4
 
5
5
  Requires Node.js `>=20.19.0`.
6
6
 
@@ -26,12 +26,12 @@ pnpm add -D eslint-plugin-react-x eslint-plugin-react-debug eslint-plugin-react-
26
26
  # Tailwind CSS
27
27
  pnpm add -D eslint-plugin-tailwindcss tailwindcss
28
28
 
29
- # .gitignore support
30
- pnpm add -D eslint-config-flat-gitignore
31
-
32
- # Vitest
33
- pnpm add -D @vitest/eslint-plugin vitest
34
- ```
29
+ # .gitignore support
30
+ pnpm add -D eslint-config-flat-gitignore
31
+
32
+ # Vitest
33
+ pnpm add -D @vitest/eslint-plugin vitest
34
+ ```
35
35
 
36
36
  ## Usage
37
37
 
@@ -52,55 +52,55 @@ Enabled by default:
52
52
  - import validation
53
53
  - Perfectionist with `recommended-alphabetical`
54
54
 
55
- Optional: `ts`, `react`, `tailwindcss`, `gitignore`, and `vitest`.
56
-
57
- More options are documented in the exported TypeScript types.
58
-
59
- The potentially expensive module-resolution rules are disabled by default. Enable
60
- them explicitly when your project needs them:
61
-
62
- ```ts
63
- export default waltz({
64
- imports: {
65
- overrides: {
66
- 'imports/named': 'error',
67
- 'imports/namespace': 'error',
68
- },
69
- },
70
- })
71
- ```
72
-
73
- ## TypeScript file scope
74
-
75
- When `ts.files` is provided, it is the maximum TypeScript scope used by the
76
- other modules that automatically include TypeScript files. JavaScript files
77
- keep their normal scope, and a module's own `files` option can override this
78
- limit. Vitest additionally intersects its test-file patterns with this scope.
79
-
80
- ```ts
81
- export default waltz({
82
- ts: { files: ['src/**/*.ts'] },
83
- react: true,
84
- })
85
- ```
86
-
87
- ## VS Code synchronization
88
-
89
- Run this command from your project root to enable ESLint fix-on-save settings
90
- while preserving existing `.vscode/settings.json` values and comments:
91
-
92
- ```bash
93
- npx eslint-waltz sync-vscode
94
- ```
95
-
96
- React plugin settings can be customized when needed:
97
-
98
- ```ts
99
- export default waltz({
100
- react: {
101
- settings: {
102
- 'react-x': { importSource: 'preact' },
103
- },
104
- },
105
- })
106
- ```
55
+ Optional: `ts`, `react`, `tailwindcss`, `gitignore`, and `vitest`.
56
+
57
+ More options are documented in the exported TypeScript types.
58
+
59
+ The potentially expensive module-resolution rules are disabled by default. Enable
60
+ them explicitly when your project needs them:
61
+
62
+ ```ts
63
+ export default waltz({
64
+ imports: {
65
+ overrides: {
66
+ 'imports/named': 'error',
67
+ 'imports/namespace': 'error',
68
+ },
69
+ },
70
+ })
71
+ ```
72
+
73
+ ## TypeScript file scope
74
+
75
+ When `ts.files` is provided, it is the maximum TypeScript scope used by the
76
+ other modules that automatically include TypeScript files. JavaScript files
77
+ keep their normal scope, and a module's own `files` option can override this
78
+ limit. Vitest additionally intersects its test-file patterns with this scope.
79
+
80
+ ```ts
81
+ export default waltz({
82
+ ts: { files: ['src/**/*.ts'] },
83
+ react: true,
84
+ })
85
+ ```
86
+
87
+ ## VS Code synchronization
88
+
89
+ Run this command from your project root to enable ESLint fix-on-save settings
90
+ while preserving existing `.vscode/settings.json` values and comments:
91
+
92
+ ```bash
93
+ npx eslint-waltz sync-vscode
94
+ ```
95
+
96
+ React plugin settings can be customized when needed:
97
+
98
+ ```ts
99
+ export default waltz({
100
+ react: {
101
+ settings: {
102
+ 'react-x': { importSource: 'preact' },
103
+ },
104
+ },
105
+ })
106
+ ```
package/dist/cli.js CHANGED
File without changes
package/dist/index.js CHANGED
@@ -20,49 +20,49 @@ function isMissingPeerError(error, moduleName) {
20
20
  }
21
21
 
22
22
  function importGitignoreSeek() {
23
- return importDefault('eslint-config-flat-gitignore', 'Gitignore support');
23
+ return importDefault('eslint-config-flat-gitignore', 'Gitignore support', () => import('eslint-config-flat-gitignore'));
24
24
  }
25
25
  function importImportXPlugin() {
26
- return importDefault('eslint-plugin-import-x', 'Import validation');
26
+ return importDefault('eslint-plugin-import-x', 'Import validation', () => import('eslint-plugin-import-x'));
27
27
  }
28
28
  function importJsoncParser() {
29
29
  return importPeer('jsonc-eslint-parser', 'JSON/JSONC support', () => import('jsonc-eslint-parser'));
30
30
  }
31
31
  function importJsoncPlugin() {
32
- return importDefaultAs('eslint-plugin-jsonc', d => d, 'JSON/JSONC support');
32
+ return importDefaultAs('eslint-plugin-jsonc', 'JSON/JSONC support', () => import('eslint-plugin-jsonc'), d => d);
33
33
  }
34
34
  function importPerfectionistPlugin() {
35
- return importDefault('eslint-plugin-perfectionist', 'Perfectionist support');
35
+ return importDefault('eslint-plugin-perfectionist', 'Perfectionist support', () => import('eslint-plugin-perfectionist'));
36
36
  }
37
37
  function importReactDebugPlugin() {
38
- return importDefaultAs('eslint-plugin-react-debug', d => d, 'React support');
38
+ return importDefaultAs('eslint-plugin-react-debug', 'React support', () => import('eslint-plugin-react-debug'), d => d);
39
39
  }
40
40
  function importReactDomPlugin() {
41
- return importDefaultAs('eslint-plugin-react-dom', d => d, 'React support');
41
+ return importDefaultAs('eslint-plugin-react-dom', 'React support', () => import('eslint-plugin-react-dom'), d => d);
42
42
  }
43
43
  function importReactHookExtraPlugin() {
44
- return importDefaultAs('eslint-plugin-react-hooks-extra', d => d, 'React support');
44
+ return importDefaultAs('eslint-plugin-react-hooks-extra', 'React support', () => import('eslint-plugin-react-hooks-extra'), d => d);
45
45
  }
46
46
  function importReactNamingConventionPlugin() {
47
- return importDefaultAs('eslint-plugin-react-naming-convention', d => d, 'React support');
47
+ return importDefaultAs('eslint-plugin-react-naming-convention', 'React support', () => import('eslint-plugin-react-naming-convention'), d => d);
48
48
  }
49
49
  function importReactPlugin() {
50
- return importDefaultAs('eslint-plugin-react-x', d => d, 'React support');
50
+ return importDefaultAs('eslint-plugin-react-x', 'React support', () => import('eslint-plugin-react-x'), d => d);
51
51
  }
52
52
  function importReactWebApiPlugin() {
53
- return importDefaultAs('eslint-plugin-react-web-api', d => d, 'React support');
53
+ return importDefaultAs('eslint-plugin-react-web-api', 'React support', () => import('eslint-plugin-react-web-api'), d => d);
54
54
  }
55
55
  function importStylisticPlugin() {
56
- return importDefault('@stylistic/eslint-plugin', 'Stylistic rules');
56
+ return importDefault('@stylistic/eslint-plugin', 'Stylistic rules', () => import('@stylistic/eslint-plugin'));
57
57
  }
58
58
  function importTailwindcssPlugin() {
59
- return importDefault('eslint-plugin-tailwindcss', 'Tailwind CSS support');
59
+ return importDefaultAs('eslint-plugin-tailwindcss', 'Tailwind CSS support', () => import('eslint-plugin-tailwindcss'), d => d);
60
60
  }
61
61
  function importTypeScriptParser() {
62
- return importDefaultAs('typescript-eslint', d => d.parser, 'TypeScript support');
62
+ return importDefaultAs('typescript-eslint', 'TypeScript support', () => import('typescript-eslint'), d => d.parser);
63
63
  }
64
64
  function importTypeScriptPlugin() {
65
- return importDefaultAs('typescript-eslint', d => d.plugin, 'TypeScript support');
65
+ return importDefaultAs('typescript-eslint', 'TypeScript support', () => import('typescript-eslint'), d => d.plugin);
66
66
  }
67
67
  function importTypeScriptRecommendedRules() {
68
68
  return importPeer('typescript-eslint', 'TypeScript support', () => import('typescript-eslint').then(({ configs }) => Object.assign({}, ...configs.recommended.map(config => config.rules ?? {}))));
@@ -70,15 +70,11 @@ function importTypeScriptRecommendedRules() {
70
70
  function importVitestPlugin() {
71
71
  return importPeer('@vitest/eslint-plugin', 'Vitest support', () => import('@vitest/eslint-plugin').then(module => module.default));
72
72
  }
73
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
74
- function importDefault(moduleName, featureName) {
75
- return importPeer(moduleName, featureName, () => import(moduleName).then(m => m.default));
73
+ function importDefault(moduleName, featureName, importer) {
74
+ return importPeer(moduleName, featureName, () => importer().then(m => m.default));
76
75
  }
77
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
78
- function importDefaultAs(moduleName,
79
- // eslint-disable-next-line @typescript-eslint/no-explicit-any
80
- cast, featureName) {
81
- return importPeer(moduleName, featureName, () => import(moduleName).then(m => cast(m.default)));
76
+ function importDefaultAs(moduleName, featureName, importer, cast) {
77
+ return importPeer(moduleName, featureName, () => importer().then(m => cast(m.default)));
82
78
  }
83
79
 
84
80
  async function gitignore(enable) {
@@ -95,6 +91,33 @@ const VITEST_TS_FILES = '**/*.{test,spec}.?([cm])ts?(x)';
95
91
  const JSON_FILES = '**/*.{json,json5,jsonc}';
96
92
  const PACKAGE_JSON_FILES = ['**/package.json'];
97
93
  const TS_CONFIG_FILES = ['**/[jt]sconfig.json', '**/[jt]sconfig.*.json'];
94
+ const INTERNAL_IMPORT_GLOB = '@/**';
95
+ function getImportOrderGroups() {
96
+ return [
97
+ 'type',
98
+ 'builtin',
99
+ 'external',
100
+ 'internal',
101
+ 'parent',
102
+ 'sibling',
103
+ 'index',
104
+ 'object',
105
+ 'unknown',
106
+ ];
107
+ }
108
+ function getPerfectionistImportGroups() {
109
+ return [
110
+ 'type',
111
+ 'builtin',
112
+ 'external',
113
+ 'internal',
114
+ 'parent',
115
+ 'sibling',
116
+ 'index',
117
+ 'style',
118
+ 'unknown',
119
+ ];
120
+ }
98
121
 
99
122
  function getScriptFiles(scope) {
100
123
  return scope.isEnableTypeScript
@@ -146,6 +169,22 @@ function resolveFileScopes(options) {
146
169
  const scriptScope = getScriptScope(isEnableTypeScript, configuredTypeScriptFiles);
147
170
  return { scriptScope };
148
171
  }
172
+ function resolveScriptFiles(scope, configuredFiles) {
173
+ const files = resolveFiles(configuredFiles, getScriptFiles(scope));
174
+ const typeScriptFiles = configuredFiles
175
+ ? intersectFiles(configuredFiles, scope.typeScriptFiles)
176
+ : scope.typeScriptFiles;
177
+ const parserTypeScriptFiles = configuredFiles
178
+ && scope.isEnableTypeScript
179
+ && scope.isCustomTypeScriptFiles
180
+ ? intersectFiles(configuredFiles, [TS_FILES])
181
+ : [];
182
+ return {
183
+ files,
184
+ parserTypeScriptFiles,
185
+ typeScriptFiles,
186
+ };
187
+ }
149
188
 
150
189
  async function importsConfig(scriptScope, options = {}) {
151
190
  const importsPlugin = await importImportXPlugin();
@@ -161,21 +200,13 @@ async function importsConfig(scriptScope, options = {}) {
161
200
  orderImportKind: 'asc',
162
201
  },
163
202
  'distinctGroup': false,
164
- 'groups': [
165
- 'builtin',
166
- 'external',
167
- 'parent',
168
- 'sibling',
169
- 'index',
170
- 'type',
171
- ['object', 'unknown'],
172
- ],
203
+ 'groups': getImportOrderGroups(),
173
204
  'named': true,
174
205
  'newlines-between': 'always',
175
206
  'pathGroups': [
176
207
  {
177
208
  group: 'internal',
178
- pattern: '@/**',
209
+ pattern: INTERNAL_IMPORT_GLOB,
179
210
  position: 'before',
180
211
  },
181
212
  {
@@ -191,7 +222,7 @@ async function importsConfig(scriptScope, options = {}) {
191
222
  position: 'after',
192
223
  },
193
224
  ],
194
- 'pathGroupsExcludedImportTypes': ['builtin'],
225
+ 'pathGroupsExcludedImportTypes': ['builtin', 'type'],
195
226
  'warnOnUnassignedImports': true,
196
227
  },
197
228
  ],
@@ -538,6 +569,8 @@ async function perfectionistConfig(options = {}, scriptScope) {
538
569
  const ruleFiles = resolveFiles(options.files, getScriptFiles(scriptScope));
539
570
  const preset = options.preset ?? 'recommended-alphabetical';
540
571
  const recommendedRules = perfectionist.configs[preset].rules ?? {};
572
+ const importSortOptions = getRuleOptions(recommendedRules['perfectionist/sort-imports']);
573
+ const exportSortOptions = getRuleOptions(recommendedRules['perfectionist/sort-exports']);
541
574
  return [
542
575
  {
543
576
  files: ruleFiles,
@@ -562,11 +595,37 @@ async function perfectionistConfig(options = {}, scriptScope) {
562
595
  ...(options.settings ? { settings: options.settings } : {}),
563
596
  rules: {
564
597
  ...recommendedRules,
598
+ 'perfectionist/sort-exports': [
599
+ 'error',
600
+ {
601
+ ...exportSortOptions,
602
+ newlinesBetween: 1,
603
+ },
604
+ ],
605
+ 'perfectionist/sort-imports': [
606
+ 'error',
607
+ {
608
+ ...importSortOptions,
609
+ groups: getPerfectionistImportGroups(),
610
+ internalPattern: ['^@/.+'],
611
+ },
612
+ ],
565
613
  ...options.overrides,
566
614
  },
567
615
  },
568
616
  ];
569
617
  }
618
+ function getRuleOptions(rule) {
619
+ if (!Array.isArray(rule)) {
620
+ return {};
621
+ }
622
+ const options = rule[1];
623
+ return typeof options === 'object'
624
+ && options !== null
625
+ && !Array.isArray(options)
626
+ ? options
627
+ : {};
628
+ }
570
629
 
571
630
  async function reactConfig(options = {}, scriptScope) {
572
631
  const [reactPlugin, reactDebugPlugin, reactDomPlugin, reactHookExtraPlugin, reactNamingConventionPlugin, reactWebApiPlugin,] = await Promise.all([
@@ -578,17 +637,8 @@ async function reactConfig(options = {}, scriptScope) {
578
637
  importReactWebApiPlugin(),
579
638
  ]);
580
639
  const resolvedConfig = typeof options === 'boolean' ? {} : options;
581
- const scriptFiles = getScriptFiles(scriptScope);
582
- const ruleFiles = resolveFiles(resolvedConfig.files, scriptFiles);
640
+ const { files: ruleFiles, parserTypeScriptFiles: customTypeScriptFiles, typeScriptFiles: typeScriptRuleFiles, } = resolveScriptFiles(scriptScope, resolvedConfig.files);
583
641
  const setupFiles = ruleFiles;
584
- const typeScriptRuleFiles = resolvedConfig.files
585
- ? intersectFiles(resolvedConfig.files, scriptScope.typeScriptFiles)
586
- : scriptScope.typeScriptFiles;
587
- const customTypeScriptFiles = resolvedConfig.files
588
- && scriptScope.isEnableTypeScript
589
- && scriptScope.isCustomTypeScriptFiles
590
- ? intersectFiles(resolvedConfig.files, [TS_FILES])
591
- : [];
592
642
  const typeScriptParser = customTypeScriptFiles.length > 0
593
643
  ? await importTypeScriptParser()
594
644
  : undefined;
@@ -721,74 +771,6 @@ async function reactConfig(options = {}, scriptScope) {
721
771
  async function stylisticConfig(options = {}, scriptScope) {
722
772
  const eslintStylistic = await importStylisticPlugin();
723
773
  const ruleFiles = resolveFiles(options.files, getScriptFiles(scriptScope));
724
- // Directly use the generated recommendation rules
725
- const configs = eslintStylistic.configs.customize();
726
- // This will enable the following rules.
727
- // '@stylistic/array-bracket-spacing'
728
- // '@stylistic/arrow-parens'
729
- // '@stylistic/arrow-spacing'
730
- // '@stylistic/block-spacing'
731
- // '@stylistic/brace-style'
732
- // '@stylistic/comma-dangle'
733
- // '@stylistic/comma-spacing'
734
- // '@stylistic/comma-style'
735
- // '@stylistic/computed-property-spacing'
736
- // '@stylistic/dot-location'
737
- // '@stylistic/eol-last'
738
- // '@stylistic/generator-star-spacing'
739
- // '@stylistic/indent'
740
- // '@stylistic/indent-binary-ops'
741
- // '@stylistic/key-spacing'
742
- // '@stylistic/keyword-spacing'
743
- // '@stylistic/lines-between-class-members'
744
- // '@stylistic/max-statements-per-line'
745
- // '@stylistic/member-delimiter-style'
746
- // '@stylistic/multiline-ternary'
747
- // '@stylistic/new-parens'
748
- // '@stylistic/no-extra-parens'
749
- // '@stylistic/no-floating-decimal'
750
- // '@stylistic/no-mixed-operators'
751
- // '@stylistic/no-mixed-spaces-and-tabs'
752
- // '@stylistic/no-multi-spaces'
753
- // '@stylistic/no-multiple-empty-lines'
754
- // '@stylistic/no-tabs'
755
- // '@stylistic/no-trailing-spaces'
756
- // '@stylistic/no-whitespace-before-property'
757
- // '@stylistic/object-curly-spacing'
758
- // '@stylistic/operator-linebreak'
759
- // '@stylistic/padded-blocks'
760
- // '@stylistic/quote-props'
761
- // '@stylistic/quotes'
762
- // '@stylistic/rest-spread-spacing'
763
- // '@stylistic/semi'
764
- // '@stylistic/semi-spacing'
765
- // '@stylistic/space-before-blocks'
766
- // '@stylistic/space-before-function-paren'
767
- // '@stylistic/space-in-parens'
768
- // '@stylistic/space-infix-ops'
769
- // '@stylistic/space-unary-ops'
770
- // '@stylistic/spaced-comment'
771
- // '@stylistic/template-curly-spacing'
772
- // '@stylistic/template-tag-spacing'
773
- // '@stylistic/type-annotation-spacing'
774
- // '@stylistic/type-generic-spacing'
775
- // '@stylistic/type-named-tuple-spacing'
776
- // '@stylistic/wrap-iife'
777
- // '@stylistic/yield-star-spacing'
778
- // '@stylistic/jsx-closing-bracket-location'
779
- // '@stylistic/jsx-closing-tag-location'
780
- // '@stylistic/jsx-curly-brace-presence'
781
- // '@stylistic/jsx-curly-newline'
782
- // '@stylistic/jsx-curly-spacing'
783
- // '@stylistic/jsx-equals-spacing'
784
- // '@stylistic/jsx-first-prop-new-line'
785
- // '@stylistic/jsx-function-call-newline'
786
- // '@stylistic/jsx-indent-props'
787
- // '@stylistic/jsx-max-props-per-line'
788
- // '@stylistic/jsx-one-expression-per-line'
789
- // '@stylistic/jsx-quotes'
790
- // '@stylistic/jsx-tag-spacing'
791
- // '@stylistic/jsx-wrap-multilines'
792
774
  return [
793
775
  {
794
776
  files: ruleFiles,
@@ -801,7 +783,227 @@ async function stylisticConfig(options = {}, scriptScope) {
801
783
  files: ruleFiles,
802
784
  name: 'waltz/stylistic/rules',
803
785
  rules: {
804
- ...configs.rules,
786
+ '@stylistic/array-bracket-spacing': ['error', 'never'],
787
+ '@stylistic/arrow-parens': [
788
+ 'error',
789
+ 'as-needed',
790
+ { requireForBlockBody: true },
791
+ ],
792
+ '@stylistic/arrow-spacing': [
793
+ 'error',
794
+ { after: true, before: true },
795
+ ],
796
+ '@stylistic/block-spacing': ['error', 'always'],
797
+ '@stylistic/brace-style': [
798
+ 'error',
799
+ 'stroustrup',
800
+ { allowSingleLine: true },
801
+ ],
802
+ '@stylistic/comma-dangle': ['error', 'always-multiline'],
803
+ '@stylistic/comma-spacing': [
804
+ 'error',
805
+ { after: true, before: false },
806
+ ],
807
+ '@stylistic/comma-style': ['error', 'last'],
808
+ '@stylistic/computed-property-spacing': [
809
+ 'error',
810
+ 'never',
811
+ { enforceForClassMembers: true },
812
+ ],
813
+ '@stylistic/dot-location': ['error', 'property'],
814
+ '@stylistic/eol-last': 'error',
815
+ '@stylistic/generator-star-spacing': [
816
+ 'error',
817
+ { after: true, before: false },
818
+ ],
819
+ '@stylistic/indent': [
820
+ 'error',
821
+ 2,
822
+ {
823
+ ArrayExpression: 1,
824
+ CallExpression: { arguments: 1 },
825
+ flatTernaryExpressions: false,
826
+ FunctionDeclaration: {
827
+ body: 1,
828
+ parameters: 1,
829
+ returnType: 1,
830
+ },
831
+ FunctionExpression: {
832
+ body: 1,
833
+ parameters: 1,
834
+ returnType: 1,
835
+ },
836
+ ignoreComments: false,
837
+ ignoredNodes: ['TSUnionType', 'TSIntersectionType'],
838
+ ImportDeclaration: 1,
839
+ MemberExpression: 1,
840
+ ObjectExpression: 1,
841
+ offsetTernaryExpressions: true,
842
+ outerIIFEBody: 1,
843
+ SwitchCase: 1,
844
+ tabLength: 2,
845
+ VariableDeclarator: 1,
846
+ },
847
+ ],
848
+ '@stylistic/indent-binary-ops': ['error', 2],
849
+ '@stylistic/jsx-closing-bracket-location': 'error',
850
+ '@stylistic/jsx-closing-tag-location': 'error',
851
+ '@stylistic/jsx-curly-brace-presence': [
852
+ 'error',
853
+ { propElementValues: 'always' },
854
+ ],
855
+ '@stylistic/jsx-curly-newline': 'error',
856
+ '@stylistic/jsx-curly-spacing': ['error', 'never'],
857
+ '@stylistic/jsx-equals-spacing': 'error',
858
+ '@stylistic/jsx-first-prop-new-line': 'error',
859
+ '@stylistic/jsx-function-call-newline': ['error', 'multiline'],
860
+ '@stylistic/jsx-indent-props': ['error', 2],
861
+ '@stylistic/jsx-max-props-per-line': [
862
+ 'error',
863
+ { maximum: 1, when: 'multiline' },
864
+ ],
865
+ '@stylistic/jsx-one-expression-per-line': [
866
+ 'error',
867
+ { allow: 'single-child' },
868
+ ],
869
+ '@stylistic/jsx-quotes': 'error',
870
+ '@stylistic/jsx-tag-spacing': [
871
+ 'error',
872
+ {
873
+ afterOpening: 'never',
874
+ beforeClosing: 'never',
875
+ beforeSelfClosing: 'always',
876
+ closingSlash: 'never',
877
+ },
878
+ ],
879
+ '@stylistic/jsx-wrap-multilines': [
880
+ 'error',
881
+ {
882
+ arrow: 'parens-new-line',
883
+ assignment: 'parens-new-line',
884
+ condition: 'parens-new-line',
885
+ declaration: 'parens-new-line',
886
+ logical: 'parens-new-line',
887
+ prop: 'parens-new-line',
888
+ propertyValue: 'parens-new-line',
889
+ return: 'parens-new-line',
890
+ },
891
+ ],
892
+ '@stylistic/key-spacing': [
893
+ 'error',
894
+ { afterColon: true, beforeColon: false },
895
+ ],
896
+ '@stylistic/keyword-spacing': [
897
+ 'error',
898
+ { after: true, before: true },
899
+ ],
900
+ '@stylistic/lines-between-class-members': [
901
+ 'error',
902
+ 'always',
903
+ { exceptAfterSingleLine: true },
904
+ ],
905
+ '@stylistic/max-statements-per-line': ['error', { max: 1 }],
906
+ '@stylistic/member-delimiter-style': [
907
+ 'error',
908
+ {
909
+ multiline: {
910
+ delimiter: 'none',
911
+ requireLast: false,
912
+ },
913
+ multilineDetection: 'brackets',
914
+ overrides: {
915
+ interface: {
916
+ multiline: {
917
+ delimiter: 'none',
918
+ requireLast: false,
919
+ },
920
+ },
921
+ },
922
+ singleline: { delimiter: 'comma' },
923
+ },
924
+ ],
925
+ '@stylistic/multiline-ternary': ['error', 'always-multiline'],
926
+ '@stylistic/new-parens': 'error',
927
+ '@stylistic/no-extra-parens': ['error', 'functions'],
928
+ '@stylistic/no-floating-decimal': 'error',
929
+ '@stylistic/no-mixed-operators': [
930
+ 'error',
931
+ {
932
+ allowSamePrecedence: true,
933
+ groups: [
934
+ ['==', '!=', '===', '!==', '>', '>=', '<', '<='],
935
+ ['&&', '||'],
936
+ ['in', 'instanceof'],
937
+ ],
938
+ },
939
+ ],
940
+ '@stylistic/no-mixed-spaces-and-tabs': 'error',
941
+ '@stylistic/no-multi-spaces': 'error',
942
+ '@stylistic/no-multiple-empty-lines': [
943
+ 'error',
944
+ { max: 1, maxBOF: 0, maxEOF: 0 },
945
+ ],
946
+ '@stylistic/no-tabs': 'error',
947
+ '@stylistic/no-trailing-spaces': 'error',
948
+ '@stylistic/no-whitespace-before-property': 'error',
949
+ '@stylistic/object-curly-spacing': ['error', 'always'],
950
+ '@stylistic/operator-linebreak': ['error', 'before'],
951
+ '@stylistic/padded-blocks': [
952
+ 'error',
953
+ { blocks: 'never', classes: 'never', switches: 'never' },
954
+ ],
955
+ '@stylistic/quote-props': ['error', 'consistent-as-needed'],
956
+ '@stylistic/quotes': [
957
+ 'error',
958
+ 'single',
959
+ { allowTemplateLiterals: 'always', avoidEscape: false },
960
+ ],
961
+ '@stylistic/rest-spread-spacing': ['error', 'never'],
962
+ '@stylistic/semi': ['error', 'never'],
963
+ '@stylistic/semi-spacing': [
964
+ 'error',
965
+ { after: true, before: false },
966
+ ],
967
+ '@stylistic/space-before-blocks': ['error', 'always'],
968
+ '@stylistic/space-before-function-paren': [
969
+ 'error',
970
+ { anonymous: 'always', asyncArrow: 'always', named: 'never' },
971
+ ],
972
+ '@stylistic/space-in-parens': ['error', 'never'],
973
+ '@stylistic/space-infix-ops': 'error',
974
+ '@stylistic/space-unary-ops': [
975
+ 'error',
976
+ { nonwords: false, words: true },
977
+ ],
978
+ '@stylistic/spaced-comment': [
979
+ 'error',
980
+ 'always',
981
+ {
982
+ block: {
983
+ balanced: true,
984
+ exceptions: ['*'],
985
+ markers: ['!'],
986
+ },
987
+ line: {
988
+ exceptions: ['/', '#'],
989
+ markers: ['/'],
990
+ },
991
+ },
992
+ ],
993
+ '@stylistic/template-curly-spacing': 'error',
994
+ '@stylistic/template-tag-spacing': ['error', 'never'],
995
+ '@stylistic/type-annotation-spacing': ['error', {}],
996
+ '@stylistic/type-generic-spacing': 'error',
997
+ '@stylistic/type-named-tuple-spacing': 'error',
998
+ '@stylistic/wrap-iife': [
999
+ 'error',
1000
+ 'any',
1001
+ { functionPrototypeMethods: true },
1002
+ ],
1003
+ '@stylistic/yield-star-spacing': [
1004
+ 'error',
1005
+ { after: true, before: false },
1006
+ ],
805
1007
  ...options.overrides,
806
1008
  },
807
1009
  },
@@ -810,12 +1012,17 @@ async function stylisticConfig(options = {}, scriptScope) {
810
1012
 
811
1013
  async function tailwindcssConfig(options = {}, scriptScope) {
812
1014
  const tailwindcss = await importTailwindcssPlugin();
813
- const recommended = tailwindcss.configs.recommended;
814
1015
  const files = resolveFiles(options.files, getScriptFiles(scriptScope));
815
1016
  return [
816
1017
  {
817
1018
  files,
818
- languageOptions: recommended.languageOptions,
1019
+ languageOptions: {
1020
+ parserOptions: {
1021
+ // Since Tailwind can be enabled independently—even without enabling
1022
+ // React—it ensures that JSX files are parsed correctly.
1023
+ ecmaFeatures: { jsx: true },
1024
+ },
1025
+ },
819
1026
  name: 'waltz/tailwindcss/setup',
820
1027
  plugins: {
821
1028
  tailwindcss,
@@ -825,11 +1032,21 @@ async function tailwindcssConfig(options = {}, scriptScope) {
825
1032
  files,
826
1033
  name: 'waltz/tailwindcss/rules',
827
1034
  rules: {
828
- ...recommended.rules,
1035
+ 'tailwindcss/classnames-order': 'warn',
1036
+ 'tailwindcss/enforces-canonical-classname': 'warn',
1037
+ 'tailwindcss/enforces-negative-arbitrary-values': 'warn',
1038
+ 'tailwindcss/enforces-shorthand': 'warn',
1039
+ 'tailwindcss/important-modifier-suffix': 'warn',
1040
+ 'tailwindcss/no-arbitrary-value': 'off',
1041
+ 'tailwindcss/no-contradicting-classname': 'error',
1042
+ // Generally, custom class names are supported, so the recommended practice
1043
+ // is not followed here.
1044
+ 'tailwindcss/no-custom-classname': 'off',
1045
+ 'tailwindcss/no-unnecessary-arbitrary-value': 'warn',
829
1046
  ...options.overrides,
830
1047
  },
831
1048
  settings: {
832
- ...recommended.settings,
1049
+ tailwindcss: {},
833
1050
  ...options.settings,
834
1051
  },
835
1052
  },
package/dist/typegen.d.ts CHANGED
@@ -1,3 +1,4 @@
1
+ // @ts-nocheck
1
2
  import { Linter } from 'eslint';
2
3
 
3
4
  interface RuleOptions {
package/dist/types.d.ts CHANGED
@@ -1,8 +1,6 @@
1
+ import { RuleOptions } from './typegen.js';
1
2
  import { Linter } from 'eslint';
2
- import { FlatGitignoreOptions } from 'eslint-config-flat-gitignore';
3
- import { PluginSettings } from 'eslint-plugin-tailwindcss';
4
3
  import globals from 'globals';
5
- import { RuleOptions } from './typegen.js';
6
4
 
7
5
  type ImportsOptions = SharedOptions;
8
6
  type JsoncOptions = SharedOptions;
@@ -214,6 +212,26 @@ interface WaltzOptions {
214
212
  vitest?: boolean | VitestOptions;
215
213
  }
216
214
  type Arraiable<T> = Array<T> | T;
215
+ interface FlatGitignoreOptions {
216
+ cwd?: string;
217
+ files?: string | string[];
218
+ filesGitModules?: string | string[];
219
+ name?: string;
220
+ recursive?: {
221
+ skipDirs: string[];
222
+ } | boolean;
223
+ root?: boolean;
224
+ strict?: boolean;
225
+ }
226
+ interface PluginSettings {
227
+ attributes?: string[];
228
+ cacheMaxAge?: number;
229
+ cacheMaxSize?: number;
230
+ cssConfigPath: string;
231
+ functions?: string[];
232
+ ignoredKeys?: string[];
233
+ parseKeyFunctions?: string[];
234
+ }
217
235
  type Rules = Record<string, Linter.RuleEntry | undefined> & RuleOptions;
218
236
 
219
237
  export type { ImportsOptions, JsOptions, JsoncOptions, LinterConfig, PerfectionistOptions, PerfectionistPreset, ReactOptions, SharedOptions, StylisticOptions, TailwindcssOptions, TsOptions, UnresolvedLinterConfig, VitestOptions, WaltzOptions };
package/package.json CHANGED
@@ -1,148 +1,151 @@
1
- {
2
- "name": "@chanyuxi/eslint-waltz",
3
- "type": "module",
4
- "version": "1.3.0",
5
- "description": "A composable ESLint flat config for JavaScript, TypeScript, JSONC, imports, React, Tailwind CSS, Perfectionist, and Vitest.",
6
- "author": "chanyuxi <2438149743@qq.com> (https://github.com/chanyuxi)",
7
- "license": "ISC",
8
- "homepage": "https://github.com/chanyuxi/eslint-waltz",
9
- "repository": {
10
- "type": "git",
11
- "url": "https://github.com/chanyuxi/eslint-waltz.git"
12
- },
13
- "bugs": {
14
- "url": "https://github.com/chanyuxi/eslint-waltz/issues"
15
- },
16
- "publishConfig": {
17
- "access": "public"
18
- },
19
- "keywords": [
20
- "chanyuxi",
21
- "eslint",
22
- "config",
23
- "integration"
24
- ],
25
- "exports": {
26
- ".": {
27
- "types": "./dist/index.d.ts",
28
- "import": "./dist/index.js",
29
- "default": "./dist/index.js"
30
- }
31
- },
32
- "bin": {
33
- "eslint-waltz": "./dist/cli.js"
34
- },
35
- "files": [
36
- "dist"
37
- ],
38
- "engines": {
39
- "node": ">=20.19.0"
40
- },
41
- "peerDependencies": {
42
- "@vitest/eslint-plugin": "^1.6.27",
43
- "eslint": "^10.10.0",
44
- "eslint-config-flat-gitignore": "^2.4.0",
45
- "eslint-plugin-import-x": "^4.17.1",
46
- "eslint-plugin-jsonc": "^3.4.2",
47
- "eslint-plugin-perfectionist": "^5.11.0",
48
- "eslint-plugin-react-debug": "^5.19.0",
49
- "eslint-plugin-react-dom": "^5.19.0",
50
- "eslint-plugin-react-hooks-extra": "^2.13.0",
51
- "eslint-plugin-react-naming-convention": "^5.19.0",
52
- "eslint-plugin-react-web-api": "^5.19.0",
53
- "eslint-plugin-react-x": "^5.19.0",
54
- "eslint-plugin-tailwindcss": "^4.4.0",
55
- "jsonc-eslint-parser": "^3.3.0",
56
- "tailwindcss": "^4.0.0",
57
- "typescript-eslint": "^8.70.0"
58
- },
59
- "peerDependenciesMeta": {
60
- "@vitest/eslint-plugin": {
61
- "optional": true
62
- },
63
- "eslint-config-flat-gitignore": {
64
- "optional": true
65
- },
66
- "eslint-plugin-react-debug": {
67
- "optional": true
68
- },
69
- "eslint-plugin-react-dom": {
70
- "optional": true
71
- },
72
- "eslint-plugin-react-hooks-extra": {
73
- "optional": true
74
- },
75
- "eslint-plugin-react-naming-convention": {
76
- "optional": true
77
- },
78
- "eslint-plugin-react-web-api": {
79
- "optional": true
80
- },
81
- "eslint-plugin-react-x": {
82
- "optional": true
83
- },
84
- "eslint-plugin-tailwindcss": {
85
- "optional": true
86
- },
87
- "tailwindcss": {
88
- "optional": true
89
- },
90
- "typescript-eslint": {
91
- "optional": true
92
- }
93
- },
94
- "dependencies": {
95
- "@eslint/js": "^10.0.1",
96
- "@stylistic/eslint-plugin": "^5.10.0",
97
- "globals": "^17.12.0",
98
- "jsonc-parser": "^3.3.1"
99
- },
100
- "devDependencies": {
101
- "@changesets/cli": "^3.0.2",
102
- "@commitlint/cli": "^19.8.1",
103
- "@commitlint/config-conventional": "^19.8.1",
104
- "@commitlint/types": "^19.8.1",
105
- "@rollup/plugin-commonjs": "^28.0.6",
106
- "@rollup/plugin-node-resolve": "^16.0.1",
107
- "@rollup/plugin-typescript": "^12.1.4",
108
- "@types/node": "^24.1.0",
109
- "@vitest/eslint-plugin": "^1.6.27",
110
- "eslint": "^10.10.0",
111
- "eslint-config-flat-gitignore": "^2.4.0",
112
- "eslint-plugin-import-x": "^4.17.1",
113
- "eslint-plugin-jsonc": "^3.4.2",
114
- "eslint-plugin-perfectionist": "^5.11.0",
115
- "eslint-plugin-react-debug": "^5.19.0",
116
- "eslint-plugin-react-dom": "^5.19.0",
117
- "eslint-plugin-react-hooks-extra": "^2.13.0",
118
- "eslint-plugin-react-naming-convention": "^5.19.0",
119
- "eslint-plugin-react-web-api": "^5.19.0",
120
- "eslint-plugin-react-x": "^5.19.0",
121
- "eslint-plugin-tailwindcss": "^4.4.0",
122
- "eslint-typegen": "^2.3.1",
123
- "husky": "^9.1.7",
124
- "jiti": "^2.7.0",
125
- "jsonc-eslint-parser": "^3.3.0",
126
- "rimraf": "^6.0.1",
127
- "rollup": "^4.46.2",
128
- "rollup-plugin-dts": "^6.2.1",
129
- "tailwindcss": "^4.3.3",
130
- "tslib": "^2.8.1",
131
- "tsx": "^4.20.3",
132
- "typescript": "^5.8.3",
133
- "typescript-eslint": "^8.70.0",
134
- "vitest": "^4.0.18"
135
- },
136
- "scripts": {
137
- "build": "rimraf ./dist && pnpm gen && rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript",
138
- "gen": "tsx scripts/typegen.ts",
139
- "lint": "eslint .",
140
- "lint:fix": "eslint . --fix",
141
- "typeCheck": "pnpm gen && tsc --noEmit -p tsconfig.test.json",
142
- "test": "vitest run",
143
- "check": "pnpm run typeCheck && pnpm run lint && pnpm run test",
144
- "changeset": "changeset",
145
- "version": "changeset version && pnpm install --lockfile-only",
146
- "release": "pnpm build && changeset publish"
147
- }
148
- }
1
+ {
2
+ "name": "@chanyuxi/eslint-waltz",
3
+ "type": "module",
4
+ "version": "1.3.2",
5
+ "packageManager": "pnpm@10.4.1",
6
+ "description": "A composable ESLint flat config for JavaScript, TypeScript, JSONC, imports, React, Tailwind CSS, Perfectionist, and Vitest.",
7
+ "author": "chanyuxi <2438149743@qq.com> (https://github.com/chanyuxi)",
8
+ "license": "ISC",
9
+ "homepage": "https://github.com/chanyuxi/eslint-waltz",
10
+ "repository": {
11
+ "type": "git",
12
+ "url": "git+https://github.com/chanyuxi/eslint-waltz.git"
13
+ },
14
+ "bugs": {
15
+ "url": "https://github.com/chanyuxi/eslint-waltz/issues"
16
+ },
17
+ "publishConfig": {
18
+ "access": "public"
19
+ },
20
+ "keywords": [
21
+ "chanyuxi",
22
+ "eslint",
23
+ "config",
24
+ "integration"
25
+ ],
26
+ "exports": {
27
+ ".": {
28
+ "types": "./dist/index.d.ts",
29
+ "import": "./dist/index.js",
30
+ "default": "./dist/index.js"
31
+ }
32
+ },
33
+ "bin": {
34
+ "eslint-waltz": "dist/cli.js"
35
+ },
36
+ "files": [
37
+ "dist"
38
+ ],
39
+ "engines": {
40
+ "node": ">=20.19.0"
41
+ },
42
+ "scripts": {
43
+ "build": "rimraf ./dist && pnpm gen && rollup -c rollup.config.ts --configPlugin @rollup/plugin-typescript",
44
+ "gen": "tsx scripts/typegen.ts",
45
+ "lint": "eslint .",
46
+ "lint:fix": "eslint . --fix",
47
+ "typeCheck": "pnpm gen && tsc --noEmit -p tsconfig.test.json",
48
+ "test": "vitest run",
49
+ "check": "pnpm run typeCheck && pnpm run lint && pnpm run test",
50
+ "changeset": "changeset",
51
+ "version": "changeset version && pnpm install --lockfile-only",
52
+ "release": "changeset publish",
53
+ "prepack": "pnpm build",
54
+ "prepare": "husky"
55
+ },
56
+ "peerDependencies": {
57
+ "@vitest/eslint-plugin": "^1.6.27",
58
+ "eslint": "^10.10.0",
59
+ "eslint-config-flat-gitignore": "^2.4.0",
60
+ "eslint-plugin-import-x": "^4.17.1",
61
+ "eslint-plugin-jsonc": "^3.4.2",
62
+ "eslint-plugin-perfectionist": "^5.11.0",
63
+ "eslint-plugin-react-debug": "^5.19.0",
64
+ "eslint-plugin-react-dom": "^5.19.0",
65
+ "eslint-plugin-react-hooks-extra": "^2.13.0",
66
+ "eslint-plugin-react-naming-convention": "^5.19.0",
67
+ "eslint-plugin-react-web-api": "^5.19.0",
68
+ "eslint-plugin-react-x": "^5.19.0",
69
+ "eslint-plugin-tailwindcss": "^4.4.0",
70
+ "jsonc-eslint-parser": "^3.3.0",
71
+ "tailwindcss": "^4.0.0",
72
+ "typescript-eslint": "^8.70.0"
73
+ },
74
+ "peerDependenciesMeta": {
75
+ "@vitest/eslint-plugin": {
76
+ "optional": true
77
+ },
78
+ "eslint-config-flat-gitignore": {
79
+ "optional": true
80
+ },
81
+ "eslint-plugin-react-debug": {
82
+ "optional": true
83
+ },
84
+ "eslint-plugin-react-dom": {
85
+ "optional": true
86
+ },
87
+ "eslint-plugin-react-hooks-extra": {
88
+ "optional": true
89
+ },
90
+ "eslint-plugin-react-naming-convention": {
91
+ "optional": true
92
+ },
93
+ "eslint-plugin-react-web-api": {
94
+ "optional": true
95
+ },
96
+ "eslint-plugin-react-x": {
97
+ "optional": true
98
+ },
99
+ "eslint-plugin-tailwindcss": {
100
+ "optional": true
101
+ },
102
+ "tailwindcss": {
103
+ "optional": true
104
+ },
105
+ "typescript-eslint": {
106
+ "optional": true
107
+ }
108
+ },
109
+ "dependencies": {
110
+ "@eslint/js": "^10.0.1",
111
+ "@stylistic/eslint-plugin": "^5.10.0",
112
+ "globals": "^17.12.0",
113
+ "jsonc-parser": "^3.3.1"
114
+ },
115
+ "devDependencies": {
116
+ "@changesets/cli": "^3.0.2",
117
+ "@commitlint/cli": "^19.8.1",
118
+ "@commitlint/config-conventional": "^19.8.1",
119
+ "@commitlint/types": "^19.8.1",
120
+ "@rollup/plugin-commonjs": "^28.0.6",
121
+ "@rollup/plugin-node-resolve": "^16.0.1",
122
+ "@rollup/plugin-typescript": "^12.1.4",
123
+ "@types/node": "^24.1.0",
124
+ "@vitest/eslint-plugin": "^1.6.27",
125
+ "eslint": "^10.10.0",
126
+ "eslint-config-flat-gitignore": "^2.4.0",
127
+ "eslint-plugin-import-x": "^4.17.1",
128
+ "eslint-plugin-jsonc": "^3.4.2",
129
+ "eslint-plugin-perfectionist": "^5.11.0",
130
+ "eslint-plugin-react-debug": "^5.19.0",
131
+ "eslint-plugin-react-dom": "^5.19.0",
132
+ "eslint-plugin-react-hooks-extra": "^2.13.0",
133
+ "eslint-plugin-react-naming-convention": "^5.19.0",
134
+ "eslint-plugin-react-web-api": "^5.19.0",
135
+ "eslint-plugin-react-x": "^5.19.0",
136
+ "eslint-plugin-tailwindcss": "^4.4.0",
137
+ "eslint-typegen": "^2.3.1",
138
+ "husky": "^9.1.7",
139
+ "jiti": "^2.7.0",
140
+ "jsonc-eslint-parser": "^3.3.0",
141
+ "rimraf": "^6.0.1",
142
+ "rollup": "^4.46.2",
143
+ "rollup-plugin-dts": "^6.2.1",
144
+ "tailwindcss": "^4.3.3",
145
+ "tslib": "^2.8.1",
146
+ "tsx": "^4.20.3",
147
+ "typescript": "^5.8.3",
148
+ "typescript-eslint": "^8.70.0",
149
+ "vitest": "^4.0.18"
150
+ }
151
+ }