@dauphaihau/eslint-config 0.2.0 → 0.2.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
@@ -2,7 +2,7 @@
2
2
 
3
3
  - Auto fix for formatting (aimed to be used standalone without Prettier)
4
4
  - Opinionated, but very customizable
5
- - Optional TypeScript support
5
+ - Auto-detects your tech stack (React, TypeScript, Tailwind, etc.)
6
6
  - ESLint Flat config, compose easily!
7
7
 
8
8
  ## Installation
@@ -15,9 +15,12 @@ And create `eslint.config.mjs` in your project root:
15
15
 
16
16
  ```js
17
17
  // eslint.config.mjs
18
- import dauphaihau from '@dauphaihau/eslint-config'
18
+ import dauphaihauConfig from '@dauphaihau/eslint-config'
19
+ import { defineConfig } from 'eslint/config'
19
20
 
20
- export default dauphaihau()
21
+ export default defineConfig([
22
+ ...(await dauphaihauConfig())
23
+ ])
21
24
  ```
22
25
 
23
26
  ### Add script for package.json
@@ -36,25 +39,40 @@ For example:
36
39
  ## Usage
37
40
 
38
41
  ### Basic
39
- Normally you only need to import the dauphaihau preset:
42
+ Normally you only need to import the dauphaihauConfig preset:
40
43
 
41
44
  ```js
42
45
  // eslint.config.js
43
- import dauphaihau from '@dauphaihau/eslint-config'
46
+ import dauphaihauConfig from '@dauphaihau/eslint-config'
47
+ import { defineConfig } from 'eslint/config'
44
48
 
45
- export default dauphaihau()
49
+ export default defineConfig([
50
+ ...(await dauphaihauConfig())
51
+ ])
46
52
  ```
47
53
 
48
54
  ### Customize
49
55
 
56
+ The config auto-detects your tech stack. Pass `false` to explicitly disable rules for a specific stack:
57
+
50
58
  ```js
51
59
  // eslint.config.js
52
- import dauphaihau from '@dauphaihau/eslint-config'
53
-
54
- export default dauphaihau({
55
- typescript: true,
56
- })
57
-
60
+ import dauphaihauConfig from '@dauphaihau/eslint-config'
61
+ import { defineConfig } from 'eslint/config'
62
+
63
+ export default defineConfig([
64
+ ...(await dauphaihauConfig({
65
+ tailwind: false, // disable Tailwind rules
66
+ })),
67
+
68
+ // Your configs and overrides
69
+ {
70
+ files: ['**/*.{ts,tsx}'],
71
+ rules: {
72
+ '@typescript-eslint/explicit-function-return-type': 'off',
73
+ },
74
+ },
75
+ ])
58
76
  ```
59
77
  ## License
60
78
 
package/dist/index.d.ts CHANGED
@@ -4,6 +4,7 @@ type Options = {
4
4
  typescript?: boolean;
5
5
  react?: boolean;
6
6
  vue?: boolean;
7
+ tailwind?: boolean;
7
8
  };
8
9
  /**
9
10
  * Default factory function for creating ESLint configurations.
@@ -11,10 +12,10 @@ type Options = {
11
12
  *
12
13
  * @example
13
14
  * ```ts
14
- * import dauphaihau from '@dauphaihau/eslint-config'
15
- * export default dauphaihau({ typescript: true })
15
+ * import eslintConfig from '@dauphaihau/eslint-config'
16
+ * export default eslintConfig({ typescript: true })
16
17
  * ```
17
18
  */
18
- declare function dauphaihau(options?: Options): Promise<Config[]>;
19
+ declare function eslintConfig(options?: Options): Promise<Config[]>;
19
20
 
20
- export { type Options, dauphaihau as default };
21
+ export { type Options, eslintConfig as default };
package/dist/index.js CHANGED
@@ -109,6 +109,27 @@ var ComponentFilesStrategy = class {
109
109
  return true;
110
110
  }
111
111
  };
112
+ var TailwindStrategy = class {
113
+ getName() {
114
+ return "tailwind";
115
+ }
116
+ getFilePatterns(options) {
117
+ const patterns = ["**/*.html"];
118
+ if (options.react) {
119
+ patterns.push(options.typescript ? "**/*.{jsx,tsx}" : "**/*.jsx");
120
+ }
121
+ if (options.vue) {
122
+ patterns.push("**/*.vue");
123
+ }
124
+ if (!options.react && !options.vue) {
125
+ patterns.push(options.typescript ? "**/*.{jsx,tsx}" : "**/*.jsx");
126
+ }
127
+ return patterns;
128
+ }
129
+ shouldApply(options) {
130
+ return options.tailwind === true;
131
+ }
132
+ };
112
133
 
113
134
  // src/strategies/strategy-manager.ts
114
135
  var StrategyManager = class {
@@ -122,6 +143,7 @@ var StrategyManager = class {
122
143
  this.register(new AllFilesStrategy());
123
144
  this.register(new SourceFilesStrategy());
124
145
  this.register(new ComponentFilesStrategy());
146
+ this.register(new TailwindStrategy());
125
147
  }
126
148
  /**
127
149
  * Register a new strategy.
@@ -195,6 +217,12 @@ var StrategyManager = class {
195
217
  getTestFiles(options) {
196
218
  return this.getFilePatterns("test", options);
197
219
  }
220
+ /**
221
+ * Convenience method: Get Tailwind CSS file patterns.
222
+ */
223
+ getTailwindFiles(options) {
224
+ return this.getFilePatterns("tailwind", options);
225
+ }
198
226
  };
199
227
  var strategyManager = new StrategyManager();
200
228
 
@@ -264,7 +292,14 @@ function baseConfig(options = {}) {
264
292
  // Operators
265
293
  "@stylistic/multiline-ternary": ["error", "always-multiline"],
266
294
  "@stylistic/no-mixed-operators": "error",
267
- "@stylistic/operator-linebreak": ["error", "after"],
295
+ "@stylistic/operator-linebreak": ["error", "after", {
296
+ overrides: {
297
+ "?": "before",
298
+ "|": "before",
299
+ ":": "before",
300
+ "||": "before"
301
+ }
302
+ }],
268
303
  "@stylistic/dot-location": ["error", "property"],
269
304
  // Disallow
270
305
  "@stylistic/no-multiple-empty-lines": ["error", { max: 2, maxEOF: 0 }],
@@ -515,8 +550,150 @@ async function reactConfig(options = {}) {
515
550
  ];
516
551
  }
517
552
 
553
+ // src/configs/tailwind.ts
554
+ async function tailwindConfig(options = {}) {
555
+ const tailwindFiles = strategyManager.getTailwindFiles(options);
556
+ if (tailwindFiles.length === 0) {
557
+ return [];
558
+ }
559
+ const { default: tailwind } = await import("eslint-plugin-tailwindcss");
560
+ return [
561
+ {
562
+ name: "dauphaihau/tailwind",
563
+ files: tailwindFiles,
564
+ plugins: { tailwindcss: tailwind },
565
+ settings: {
566
+ tailwindcss: {
567
+ // Common utility function callees to analyze for class names
568
+ callees: ["classnames", "clsx", "ctl", "cva", "cx", "cn"],
569
+ // Support tagged template literals: tw`bg-blue-500`
570
+ tags: ["tw"]
571
+ }
572
+ },
573
+ rules: {
574
+ "tailwindcss/classnames-order": "warn",
575
+ "tailwindcss/enforces-negative-arbitrary-values": "warn",
576
+ "tailwindcss/enforces-shorthand": "warn",
577
+ "tailwindcss/migration-from-tailwind-2": "off",
578
+ // projects target v3+
579
+ "tailwindcss/no-arbitrary-value": "off",
580
+ // too restrictive globally; colors enforced below via no-restricted-syntax
581
+ "tailwindcss/no-contradicting-classname": "error",
582
+ "tailwindcss/no-custom-classname": "warn",
583
+ "tailwindcss/no-unnecessary-arbitrary-value": "warn",
584
+ // Partial enforcement of no-arbitrary-value scoped to color utilities.
585
+ // Arbitrary color values (bg-[#fff], text-[rgba(...)]) bypass the design
586
+ // system and should use config tokens instead. Covers: bg, text, border,
587
+ // ring, fill, stroke, from, via, to, accent, caret, decoration, outline,
588
+ // placeholder, shadow, divide — for hex, rgb(), rgba(), hsl(), hsla(), oklch().
589
+ "no-restricted-syntax": [
590
+ "warn",
591
+ {
592
+ selector: "Literal[value=/(?:^|\\s)(?:bg|text|border|ring|fill|stroke|from|via|to|accent|caret|decoration|outline|placeholder|shadow|divide)-\\[(?:#[0-9a-fA-F]|rgba?\\(|hsla?\\(|oklch\\()/]",
593
+ message: "Avoid arbitrary color values (e.g. bg-[#fff]). Use a design system color token from the Tailwind config instead."
594
+ }
595
+ ]
596
+ }
597
+ }
598
+ ];
599
+ }
600
+
518
601
  // src/configs/naming.ts
519
602
  import ts2 from "typescript-eslint";
603
+ var identifierQualityRules = {
604
+ // Invalid example: const n = 'Name User'
605
+ "id-length": [
606
+ "warn",
607
+ {
608
+ min: 2,
609
+ exceptions: ["i", "j", "x", "y"]
610
+ }
611
+ ],
612
+ // Discourage vague placeholder names.
613
+ // Prefer domain-specific names such as users, payload, responseBody, or config.
614
+ "id-denylist": ["warn", "foo", "bar", "baz", "tmp", "arr", "obj", "data"]
615
+ };
616
+ var variableNamingSelectors = [
617
+ // ---------- Boolean naming ----------
618
+ {
619
+ selector: "variable",
620
+ types: ["boolean"],
621
+ format: ["PascalCase", "camelCase"],
622
+ prefix: ["is", "has", "should", "can", "did", "will"],
623
+ filter: { regex: "^(is|has|should|can|did|will)[A-Z]", match: true }
624
+ },
625
+ {
626
+ // Valid examples: apiBaseUrl, DEFAULT_TIMEOUT_MS, _internalValue
627
+ selector: "variable",
628
+ modifiers: ["const"],
629
+ format: ["camelCase", "UPPER_CASE"],
630
+ leadingUnderscore: "allow"
631
+ },
632
+ // Valid examples: userName, fetchUsers, _internalValue
633
+ {
634
+ selector: ["variable", "function"],
635
+ format: ["camelCase"],
636
+ leadingUnderscore: "allow"
637
+ // allows _privateVar
638
+ }
639
+ ];
640
+ var baseNamingSelectors = [
641
+ ...variableNamingSelectors,
642
+ // ---------- Interfaces ----------
643
+ {
644
+ selector: "interface",
645
+ format: ["PascalCase"],
646
+ custom: { regex: "^I[A-Z]", match: false }
647
+ // forbid I prefix
648
+ },
649
+ // ---------- Type Aliases ----------
650
+ {
651
+ selector: "typeAlias",
652
+ format: ["PascalCase"]
653
+ // User, UserPayload
654
+ },
655
+ // ---------- Classes ----------
656
+ {
657
+ selector: "class",
658
+ format: ["PascalCase"]
659
+ },
660
+ // ---------- Enums ----------
661
+ {
662
+ selector: "enum",
663
+ format: ["PascalCase"]
664
+ },
665
+ {
666
+ selector: "enumMember",
667
+ format: ["UPPER_CASE"]
668
+ // STATUS.OK
669
+ },
670
+ // ---------- Parameters ----------
671
+ {
672
+ selector: "parameter",
673
+ format: ["camelCase"],
674
+ leadingUnderscore: "allow"
675
+ // allow _unused
676
+ },
677
+ // ---------- Properties (object keys) ----------
678
+ {
679
+ selector: "objectLiteralProperty",
680
+ format: null
681
+ // allow anything -> API response, snake_case keys allowed
682
+ },
683
+ {
684
+ // internal domain types
685
+ selector: "typeProperty",
686
+ format: ["camelCase"]
687
+ },
688
+ // ---------- Private members ----------
689
+ {
690
+ selector: "classProperty",
691
+ modifiers: ["private"],
692
+ format: ["camelCase"],
693
+ leadingUnderscore: "allow"
694
+ // _value
695
+ }
696
+ ];
520
697
  function namingConfig(options = {}) {
521
698
  const { typescript = false } = options;
522
699
  if (!typescript) {
@@ -537,93 +714,11 @@ function namingConfig(options = {}) {
537
714
  "@typescript-eslint": ts2.plugin
538
715
  },
539
716
  rules: {
540
- // Naming conventions
541
- "@typescript-eslint/naming-convention": [
542
- "error",
543
- // ---------- Variable, Function ----------
544
- {
545
- selector: ["variable", "function"],
546
- format: ["camelCase"],
547
- leadingUnderscore: "allow"
548
- // allows _privateVar
549
- },
550
- {
551
- selector: "variable",
552
- // constants (like env, config)
553
- modifiers: ["const"],
554
- format: ["UPPER_CASE"],
555
- filter: {
556
- regex: "^[A-Z0-9_]+$",
557
- match: true
558
- }
559
- },
560
- // ---------- Interfaces ----------
561
- {
562
- selector: "interface",
563
- format: ["PascalCase"],
564
- custom: { regex: "^I[A-Z]", match: false }
565
- // forbid I prefix
566
- },
567
- // ---------- Type Aliases ----------
568
- {
569
- selector: "typeAlias",
570
- format: ["PascalCase"]
571
- // User, UserPayload
572
- },
573
- // ---------- Classes ----------
574
- {
575
- selector: "class",
576
- format: ["PascalCase"]
577
- },
578
- // ---------- Enums ----------
579
- {
580
- selector: "enum",
581
- format: ["PascalCase"]
582
- },
583
- {
584
- selector: "enumMember",
585
- format: ["UPPER_CASE"]
586
- // STATUS.OK
587
- },
588
- // ---------- Parameters ----------
589
- {
590
- selector: "parameter",
591
- format: ["camelCase"],
592
- leadingUnderscore: "allow"
593
- // allow _unused
594
- },
595
- // ---------- Properties (object keys) ----------
596
- {
597
- selector: "objectLiteralProperty",
598
- format: null
599
- // allow anything -> API response, snake_case keys allowed
600
- },
601
- {
602
- // internal domain types
603
- selector: "typeProperty",
604
- format: ["camelCase"]
605
- },
606
- // ---------- Private members ----------
607
- {
608
- selector: "classProperty",
609
- modifiers: ["private"],
610
- format: ["camelCase"],
611
- leadingUnderscore: "allow"
612
- // _value
613
- },
614
- // ---------- Boolean naming ----------
615
- {
616
- selector: "variable",
617
- types: ["boolean"],
618
- format: ["PascalCase", "camelCase"],
619
- prefix: ["is", "has", "should", "can", "did", "will"],
620
- filter: { regex: "^(is|has|should|can|did|will)[A-Z]", match: true }
621
- }
622
- ]
717
+ ...identifierQualityRules,
718
+ "@typescript-eslint/naming-convention": ["error", ...baseNamingSelectors]
623
719
  }
624
720
  },
625
- // TSX/JSX specific: Allow PascalCase for functions (React components)
626
- // Helper functions will still be camelCase due to the general rule above
721
+ // TSX/JSX specific: Allow PascalCase for functions (React components) and component variables
627
722
  {
628
723
  name: "dauphaihau/naming-tsx",
629
724
  files: componentFiles,
@@ -634,23 +729,23 @@ function namingConfig(options = {}) {
634
729
  "@typescript-eslint": ts2.plugin
635
730
  },
636
731
  rules: {
732
+ ...identifierQualityRules,
637
733
  "@typescript-eslint/naming-convention": [
638
734
  "error",
735
+ // TSX overrides come first — same-specificity selectors use first-match wins
639
736
  {
640
737
  selector: "function",
641
738
  format: ["PascalCase", "camelCase"]
642
- // Allow both for components and helpers
643
739
  },
644
740
  {
645
741
  selector: "variable",
646
742
  format: ["camelCase", "PascalCase"],
647
- // Allow PascalCase for component variables
648
743
  filter: {
649
- // Only allow PascalCase if it's likely a component (starts with uppercase)
650
- regex: "^[A-Z]",
744
+ regex: "^[A-Z][a-zA-Z0-9]*$",
651
745
  match: true
652
746
  }
653
- }
747
+ },
748
+ ...baseNamingSelectors
654
749
  ]
655
750
  }
656
751
  }
@@ -708,6 +803,7 @@ var ESLintConfigBuilder = class {
708
803
  this.fileNamesAdded = false;
709
804
  this.typescriptAdded = false;
710
805
  this.reactAdded = false;
806
+ this.tailwindAdded = false;
711
807
  }
712
808
  /**
713
809
  * Set options that will be used for all subsequent config additions.
@@ -787,6 +883,21 @@ var ESLintConfigBuilder = class {
787
883
  this.reactAdded = true;
788
884
  return this;
789
885
  }
886
+ /**
887
+ * Add Tailwind CSS-specific rules and plugins.
888
+ * Requires tailwind option to be set to true.
889
+ */
890
+ withTailwind(options) {
891
+ const mergedOptions = { ...this.options, ...options };
892
+ if (!mergedOptions.tailwind) {
893
+ console.warn(
894
+ "ESLintConfigBuilder: Tailwind config added but tailwind option is not set. Consider calling setOptions({ tailwind: true }) first."
895
+ );
896
+ }
897
+ this.pendingConfigs.push(tailwindConfig(mergedOptions));
898
+ this.tailwindAdded = true;
899
+ return this;
900
+ }
790
901
  /**
791
902
  * Add a custom config object directly.
792
903
  * Useful for adding project-specific rules or third-party configs.
@@ -816,6 +927,9 @@ var ESLintConfigBuilder = class {
816
927
  if (mergedOptions.react) {
817
928
  this.withReact(mergedOptions);
818
929
  }
930
+ if (mergedOptions.tailwind) {
931
+ this.withTailwind(mergedOptions);
932
+ }
819
933
  return this;
820
934
  }
821
935
  /**
@@ -839,6 +953,7 @@ var ESLintConfigBuilder = class {
839
953
  this.fileNamesAdded = false;
840
954
  this.typescriptAdded = false;
841
955
  this.reactAdded = false;
956
+ this.tailwindAdded = false;
842
957
  return this;
843
958
  }
844
959
  /**
@@ -868,6 +983,9 @@ var ESLintConfigBuilder = class {
868
983
  hasReact() {
869
984
  return this.reactAdded;
870
985
  }
986
+ hasTailwind() {
987
+ return this.tailwindAdded;
988
+ }
871
989
  };
872
990
 
873
991
  // src/index.ts
@@ -889,14 +1007,31 @@ var hasReact = () => {
889
1007
  }
890
1008
  return false;
891
1009
  };
892
- function dauphaihau(options = {}) {
1010
+ var hasTailwind = () => {
1011
+ try {
1012
+ const packageJsonPath = "package.json";
1013
+ if (fs2.existsSync(packageJsonPath)) {
1014
+ const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
1015
+ const deps = {
1016
+ ...packageJson.dependencies,
1017
+ ...packageJson.devDependencies,
1018
+ ...packageJson.peerDependencies
1019
+ };
1020
+ if ("tailwindcss" in deps) return true;
1021
+ }
1022
+ } catch {
1023
+ }
1024
+ return fs2.existsSync("tailwind.config.js") || fs2.existsSync("tailwind.config.ts") || fs2.existsSync("tailwind.config.mjs") || fs2.existsSync("tailwind.config.cjs");
1025
+ };
1026
+ function eslintConfig(options = {}) {
893
1027
  const finalOptions = {
894
1028
  typescript: options.typescript ?? hasTsConfig,
895
1029
  react: options.react ?? hasReact(),
1030
+ tailwind: options.tailwind ?? hasTailwind(),
896
1031
  ...options
897
1032
  };
898
1033
  return new ESLintConfigBuilder().setOptions(finalOptions).withAll().build();
899
1034
  }
900
1035
  export {
901
- dauphaihau as default
1036
+ eslintConfig as default
902
1037
  };
package/package.json CHANGED
@@ -1,6 +1,6 @@
1
1
  {
2
2
  "name": "@dauphaihau/eslint-config",
3
- "version": "0.2.0",
3
+ "version": "0.2.2",
4
4
  "type": "module",
5
5
  "main": "dist/index.js",
6
6
  "types": "dist/index.d.ts",
@@ -19,7 +19,8 @@
19
19
  "typescript-eslint": "^8.56.1"
20
20
  },
21
21
  "peerDependencies": {
22
- "eslint": "^9.0.0 || ^10.0.0"
22
+ "eslint": "^9.0.0 || ^10.0.0",
23
+ "eslint-plugin-tailwindcss": ">=3.0.0"
23
24
  },
24
25
  "peerDependenciesMeta": {
25
26
  "eslint-plugin-react": {
@@ -30,6 +31,9 @@
30
31
  },
31
32
  "eslint-plugin-react-refresh": {
32
33
  "optional": true
34
+ },
35
+ "eslint-plugin-tailwindcss": {
36
+ "optional": true
33
37
  }
34
38
  },
35
39
  "devDependencies": {
@@ -38,17 +42,22 @@
38
42
  "eslint-plugin-react": "^7.37.5",
39
43
  "eslint-plugin-react-hooks": "^7.0.1",
40
44
  "eslint-plugin-react-refresh": "^0.5.2",
45
+ "eslint-plugin-tailwindcss": "^3.18.2",
41
46
  "jiti": "^2.0.0",
42
47
  "tsup": "^8.0.0",
43
- "typescript": "^5.4.0"
48
+ "typescript": "^5.4.0",
49
+ "vitest": "^3.2.4"
44
50
  },
45
51
  "scripts": {
46
- "build": "pnpm exec tsup src/index.ts --dts --format esm --external @eslint/js --external @stylistic/eslint-plugin --external typescript-eslint --external eslint-plugin-check-file --external eslint-plugin-react --external eslint-plugin-react-hooks --external eslint-plugin-react-refresh",
52
+ "build": "pnpm exec tsup src/index.ts --dts --format esm --external @eslint/js --external @stylistic/eslint-plugin --external typescript-eslint --external eslint-plugin-check-file --external eslint-plugin-react --external eslint-plugin-react-hooks --external eslint-plugin-react-refresh --external eslint-plugin-tailwindcss",
47
53
  "lint": "eslint .",
48
54
  "lint:fix": "eslint . --fix",
55
+ "test": "vitest run",
49
56
  "typecheck": "tsc --noEmit",
50
- "release:patch": "pnpm build && pnpm version patch && pnpm publish --no-git-checks",
51
- "release:minor": "pnpm build && pnpm version minor && pnpm publish --no-git-checks",
52
- "release:major": "pnpm build && pnpm version major && pnpm publish --no-git-checks"
57
+ "version:patch": "pnpm build && pnpm version patch",
58
+ "version:minor": "pnpm build && pnpm version minor",
59
+ "version:major": "pnpm build && pnpm version major",
60
+ "push:tags": "git push --follow-tags",
61
+ "ship": "pnpm publish --no-git-checks"
53
62
  }
54
63
  }