@dauphaihau/eslint-config 0.2.0 → 0.2.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/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,154 @@ 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
+ // Valid examples: userName, fetchUsers, _internalValue
618
+ {
619
+ selector: ["variable", "function"],
620
+ format: ["camelCase"],
621
+ leadingUnderscore: "allow"
622
+ // allows _privateVar
623
+ },
624
+ {
625
+ // Enforce UPPER_CASE for exported constants.
626
+ // Valid examples: API_BASE_URL, MAX_RETRY_COUNT, DEFAULT_TIMEOUT_MS
627
+ selector: "variable",
628
+ modifiers: ["const", "exported"],
629
+ format: ["UPPER_CASE"],
630
+ filter: {
631
+ regex: "^[A-Z0-9_]+$",
632
+ match: true
633
+ }
634
+ },
635
+ // ---------- Boolean naming ----------
636
+ {
637
+ selector: "variable",
638
+ types: ["boolean"],
639
+ format: ["PascalCase", "camelCase"],
640
+ prefix: ["is", "has", "should", "can", "did", "will"],
641
+ filter: { regex: "^(is|has|should|can|did|will)[A-Z]", match: true }
642
+ }
643
+ ];
644
+ var baseNamingSelectors = [
645
+ ...variableNamingSelectors,
646
+ // ---------- Interfaces ----------
647
+ {
648
+ selector: "interface",
649
+ format: ["PascalCase"],
650
+ custom: { regex: "^I[A-Z]", match: false }
651
+ // forbid I prefix
652
+ },
653
+ // ---------- Type Aliases ----------
654
+ {
655
+ selector: "typeAlias",
656
+ format: ["PascalCase"]
657
+ // User, UserPayload
658
+ },
659
+ // ---------- Classes ----------
660
+ {
661
+ selector: "class",
662
+ format: ["PascalCase"]
663
+ },
664
+ // ---------- Enums ----------
665
+ {
666
+ selector: "enum",
667
+ format: ["PascalCase"]
668
+ },
669
+ {
670
+ selector: "enumMember",
671
+ format: ["UPPER_CASE"]
672
+ // STATUS.OK
673
+ },
674
+ // ---------- Parameters ----------
675
+ {
676
+ selector: "parameter",
677
+ format: ["camelCase"],
678
+ leadingUnderscore: "allow"
679
+ // allow _unused
680
+ },
681
+ // ---------- Properties (object keys) ----------
682
+ {
683
+ selector: "objectLiteralProperty",
684
+ format: null
685
+ // allow anything -> API response, snake_case keys allowed
686
+ },
687
+ {
688
+ // internal domain types
689
+ selector: "typeProperty",
690
+ format: ["camelCase"]
691
+ },
692
+ // ---------- Private members ----------
693
+ {
694
+ selector: "classProperty",
695
+ modifiers: ["private"],
696
+ format: ["camelCase"],
697
+ leadingUnderscore: "allow"
698
+ // _value
699
+ }
700
+ ];
520
701
  function namingConfig(options = {}) {
521
702
  const { typescript = false } = options;
522
703
  if (!typescript) {
@@ -537,93 +718,11 @@ function namingConfig(options = {}) {
537
718
  "@typescript-eslint": ts2.plugin
538
719
  },
539
720
  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
- ]
721
+ ...identifierQualityRules,
722
+ "@typescript-eslint/naming-convention": ["error", ...baseNamingSelectors]
623
723
  }
624
724
  },
625
- // TSX/JSX specific: Allow PascalCase for functions (React components)
626
- // Helper functions will still be camelCase due to the general rule above
725
+ // TSX/JSX specific: Allow PascalCase for functions (React components) and component variables
627
726
  {
628
727
  name: "dauphaihau/naming-tsx",
629
728
  files: componentFiles,
@@ -634,23 +733,23 @@ function namingConfig(options = {}) {
634
733
  "@typescript-eslint": ts2.plugin
635
734
  },
636
735
  rules: {
736
+ ...identifierQualityRules,
637
737
  "@typescript-eslint/naming-convention": [
638
738
  "error",
739
+ // TSX overrides come first — same-specificity selectors use first-match wins
639
740
  {
640
741
  selector: "function",
641
742
  format: ["PascalCase", "camelCase"]
642
- // Allow both for components and helpers
643
743
  },
644
744
  {
645
745
  selector: "variable",
646
746
  format: ["camelCase", "PascalCase"],
647
- // Allow PascalCase for component variables
648
747
  filter: {
649
- // Only allow PascalCase if it's likely a component (starts with uppercase)
650
748
  regex: "^[A-Z]",
651
749
  match: true
652
750
  }
653
- }
751
+ },
752
+ ...baseNamingSelectors
654
753
  ]
655
754
  }
656
755
  }
@@ -708,6 +807,7 @@ var ESLintConfigBuilder = class {
708
807
  this.fileNamesAdded = false;
709
808
  this.typescriptAdded = false;
710
809
  this.reactAdded = false;
810
+ this.tailwindAdded = false;
711
811
  }
712
812
  /**
713
813
  * Set options that will be used for all subsequent config additions.
@@ -787,6 +887,21 @@ var ESLintConfigBuilder = class {
787
887
  this.reactAdded = true;
788
888
  return this;
789
889
  }
890
+ /**
891
+ * Add Tailwind CSS-specific rules and plugins.
892
+ * Requires tailwind option to be set to true.
893
+ */
894
+ withTailwind(options) {
895
+ const mergedOptions = { ...this.options, ...options };
896
+ if (!mergedOptions.tailwind) {
897
+ console.warn(
898
+ "ESLintConfigBuilder: Tailwind config added but tailwind option is not set. Consider calling setOptions({ tailwind: true }) first."
899
+ );
900
+ }
901
+ this.pendingConfigs.push(tailwindConfig(mergedOptions));
902
+ this.tailwindAdded = true;
903
+ return this;
904
+ }
790
905
  /**
791
906
  * Add a custom config object directly.
792
907
  * Useful for adding project-specific rules or third-party configs.
@@ -816,6 +931,9 @@ var ESLintConfigBuilder = class {
816
931
  if (mergedOptions.react) {
817
932
  this.withReact(mergedOptions);
818
933
  }
934
+ if (mergedOptions.tailwind) {
935
+ this.withTailwind(mergedOptions);
936
+ }
819
937
  return this;
820
938
  }
821
939
  /**
@@ -839,6 +957,7 @@ var ESLintConfigBuilder = class {
839
957
  this.fileNamesAdded = false;
840
958
  this.typescriptAdded = false;
841
959
  this.reactAdded = false;
960
+ this.tailwindAdded = false;
842
961
  return this;
843
962
  }
844
963
  /**
@@ -868,6 +987,9 @@ var ESLintConfigBuilder = class {
868
987
  hasReact() {
869
988
  return this.reactAdded;
870
989
  }
990
+ hasTailwind() {
991
+ return this.tailwindAdded;
992
+ }
871
993
  };
872
994
 
873
995
  // src/index.ts
@@ -889,14 +1011,31 @@ var hasReact = () => {
889
1011
  }
890
1012
  return false;
891
1013
  };
892
- function dauphaihau(options = {}) {
1014
+ var hasTailwind = () => {
1015
+ try {
1016
+ const packageJsonPath = "package.json";
1017
+ if (fs2.existsSync(packageJsonPath)) {
1018
+ const packageJson = JSON.parse(fs2.readFileSync(packageJsonPath, "utf-8"));
1019
+ const deps = {
1020
+ ...packageJson.dependencies,
1021
+ ...packageJson.devDependencies,
1022
+ ...packageJson.peerDependencies
1023
+ };
1024
+ if ("tailwindcss" in deps) return true;
1025
+ }
1026
+ } catch {
1027
+ }
1028
+ return fs2.existsSync("tailwind.config.js") || fs2.existsSync("tailwind.config.ts") || fs2.existsSync("tailwind.config.mjs") || fs2.existsSync("tailwind.config.cjs");
1029
+ };
1030
+ function eslintConfig(options = {}) {
893
1031
  const finalOptions = {
894
1032
  typescript: options.typescript ?? hasTsConfig,
895
1033
  react: options.react ?? hasReact(),
1034
+ tailwind: options.tailwind ?? hasTailwind(),
896
1035
  ...options
897
1036
  };
898
1037
  return new ESLintConfigBuilder().setOptions(finalOptions).withAll().build();
899
1038
  }
900
1039
  export {
901
- dauphaihau as default
1040
+ eslintConfig as default
902
1041
  };
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.1",
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,19 @@
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
48
  "typescript": "^5.4.0"
44
49
  },
45
50
  "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",
51
+ "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
52
  "lint": "eslint .",
48
53
  "lint:fix": "eslint . --fix",
49
54
  "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"
55
+ "version:patch": "pnpm build && pnpm version patch",
56
+ "version:minor": "pnpm build && pnpm version minor",
57
+ "version:major": "pnpm build && pnpm version major",
58
+ "push": "git push --follow-tags"
53
59
  }
54
60
  }