uilint-eslint 0.2.29 → 0.2.30

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.
Files changed (46) hide show
  1. package/dist/index.d.ts +11 -0
  2. package/dist/index.js +162 -47
  3. package/dist/index.js.map +1 -1
  4. package/dist/rules/consistent-dark-mode.js.map +1 -1
  5. package/dist/rules/consistent-spacing.js.map +1 -1
  6. package/dist/rules/enforce-absolute-imports.js.map +1 -1
  7. package/dist/rules/no-any-in-props.js.map +1 -1
  8. package/dist/rules/no-arbitrary-tailwind.js.map +1 -1
  9. package/dist/rules/no-direct-store-import.js.map +1 -1
  10. package/dist/rules/no-mixed-component-libraries.js +6 -5
  11. package/dist/rules/no-mixed-component-libraries.js.map +1 -1
  12. package/dist/rules/no-prop-drilling-depth.js.map +1 -1
  13. package/dist/rules/no-secrets-in-code.js.map +1 -1
  14. package/dist/rules/no-semantic-duplicates.js.map +1 -1
  15. package/dist/rules/prefer-zustand-state-management.js.map +1 -1
  16. package/dist/rules/require-input-validation.js.map +1 -1
  17. package/dist/rules/require-test-coverage.js +9 -15
  18. package/dist/rules/require-test-coverage.js.map +1 -1
  19. package/dist/rules/semantic-vision.js.map +1 -1
  20. package/dist/rules/semantic.js +6 -5
  21. package/dist/rules/semantic.js.map +1 -1
  22. package/dist/rules/zustand-use-selectors.js.map +1 -1
  23. package/package.json +2 -2
  24. package/src/index.ts +17 -13
  25. package/src/rule-registry.ts +6 -5
  26. package/src/rules/{no-mixed-component-libraries.ts → no-mixed-component-libraries/index.ts} +3 -2
  27. package/src/rules/no-mixed-component-libraries.test.ts +2 -2
  28. package/src/rules/{require-test-coverage.ts → require-test-coverage/index.ts} +4 -10
  29. package/src/{utils → rules/require-test-coverage/lib}/coverage-aggregator.ts +2 -2
  30. package/src/{utils → rules/require-test-coverage/lib}/dependency-graph.ts +1 -1
  31. package/src/rules/require-test-coverage/lib/export-resolver.ts +348 -0
  32. package/src/{utils → rules/require-test-coverage/lib}/jsx-coverage-analyzer.ts +0 -26
  33. package/src/rules/require-test-coverage.test.ts +1 -1
  34. package/src/rules/{semantic.ts → semantic/index.ts} +4 -3
  35. package/src/utils/coverage-aggregator.test.ts +2 -2
  36. package/src/utils/create-rule.ts +12 -0
  37. package/src/utils/dependency-graph.test.ts +1 -1
  38. package/src/utils/file-categorizer.test.ts +1 -1
  39. package/src/utils/import-graph.test.ts +3 -3
  40. package/src/utils/jsx-coverage-analyzer.test.ts +1 -1
  41. /package/src/{utils → rules/no-mixed-component-libraries/lib}/component-parser.ts +0 -0
  42. /package/src/{utils → rules/no-mixed-component-libraries/lib}/export-resolver.ts +0 -0
  43. /package/src/{utils → rules/no-mixed-component-libraries/lib}/import-graph.ts +0 -0
  44. /package/src/{utils → rules/require-test-coverage/lib}/file-categorizer.ts +0 -0
  45. /package/src/{utils → rules/semantic/lib}/cache.ts +0 -0
  46. /package/src/{utils → rules/semantic/lib}/styleguide-loader.ts +0 -0
package/dist/index.d.ts CHANGED
@@ -79,6 +79,17 @@ interface RuleMeta {
79
79
  * Example: ["coverage-aggregator", "dependency-graph"]
80
80
  */
81
81
  internalDependencies?: string[];
82
+ /**
83
+ * Whether this rule is directory-based (has lib/ folder with utilities).
84
+ * Directory-based rules are installed as folders with index.ts and lib/ subdirectory.
85
+ * Single-file rules are installed as single .ts files.
86
+ *
87
+ * When true, ESLint config imports will use:
88
+ * ./.uilint/rules/rule-id/index.js
89
+ * When false (default):
90
+ * ./.uilint/rules/rule-id.js
91
+ */
92
+ isDirectoryBased?: boolean;
82
93
  }
83
94
  /**
84
95
  * Helper to define rule metadata with type safety
package/dist/index.js CHANGED
@@ -1108,7 +1108,7 @@ var prefer_zustand_state_management_default = createRule({
1108
1108
  }
1109
1109
  });
1110
1110
 
1111
- // src/utils/export-resolver.ts
1111
+ // src/rules/no-mixed-component-libraries/lib/export-resolver.ts
1112
1112
  import { ResolverFactory } from "oxc-resolver";
1113
1113
  import { parse } from "@typescript-eslint/typescript-estree";
1114
1114
  import { readFileSync, existsSync } from "fs";
@@ -1302,7 +1302,7 @@ function clearResolverCaches() {
1302
1302
  resolvedPathCache.clear();
1303
1303
  }
1304
1304
 
1305
- // src/utils/component-parser.ts
1305
+ // src/rules/no-mixed-component-libraries/lib/component-parser.ts
1306
1306
  var LIBRARY_PATTERNS = {
1307
1307
  shadcn: ["@/components/ui", "@radix-ui/", "components/ui/"],
1308
1308
  mui: ["@mui/material", "@mui/icons-material", "@emotion/"],
@@ -1482,7 +1482,7 @@ function parseComponentBody(filePath, componentName) {
1482
1482
  return result;
1483
1483
  }
1484
1484
 
1485
- // src/utils/import-graph.ts
1485
+ // src/rules/no-mixed-component-libraries/lib/import-graph.ts
1486
1486
  var componentLibraryCache = /* @__PURE__ */ new Map();
1487
1487
  function getComponentLibrary(contextFilePath, componentName, importSource) {
1488
1488
  const directLibrary = detectLibraryFromSource(importSource);
@@ -1600,7 +1600,7 @@ function clearCache() {
1600
1600
  clearResolverCaches();
1601
1601
  }
1602
1602
 
1603
- // src/rules/no-mixed-component-libraries.ts
1603
+ // src/rules/no-mixed-component-libraries/index.ts
1604
1604
  var meta6 = defineRuleMeta({
1605
1605
  id: "no-mixed-component-libraries",
1606
1606
  name: "No Mixed Component Libraries",
@@ -1680,7 +1680,8 @@ import { Card } from '@/components/ui/card';
1680
1680
  - **mui**: Material-UI (\`@mui/material\`, \`@mui/joy\`)
1681
1681
  - **chakra**: Chakra UI (\`@chakra-ui/react\`)
1682
1682
  - **antd**: Ant Design (\`antd\`)
1683
- `
1683
+ `,
1684
+ isDirectoryBased: true
1684
1685
  });
1685
1686
  var no_mixed_component_libraries_default = createRule({
1686
1687
  name: "no-mixed-component-libraries",
@@ -1807,12 +1808,12 @@ var no_mixed_component_libraries_default = createRule({
1807
1808
  }
1808
1809
  });
1809
1810
 
1810
- // src/rules/semantic.ts
1811
+ // src/rules/semantic/index.ts
1811
1812
  import { existsSync as existsSync4, readFileSync as readFileSync4 } from "fs";
1812
1813
  import { spawnSync } from "child_process";
1813
1814
  import { dirname as dirname4, join as join4, relative } from "path";
1814
1815
 
1815
- // src/utils/cache.ts
1816
+ // src/rules/semantic/lib/cache.ts
1816
1817
  import { existsSync as existsSync2, mkdirSync, readFileSync as readFileSync2, writeFileSync } from "fs";
1817
1818
  import { dirname as dirname2, join as join2 } from "path";
1818
1819
  var xxhashInstance = null;
@@ -1896,7 +1897,7 @@ function clearCache2(projectRoot) {
1896
1897
  saveCache(projectRoot, { version: CACHE_VERSION, entries: {} });
1897
1898
  }
1898
1899
 
1899
- // src/utils/styleguide-loader.ts
1900
+ // src/rules/semantic/lib/styleguide-loader.ts
1900
1901
  import { existsSync as existsSync3, readFileSync as readFileSync3 } from "fs";
1901
1902
  import { dirname as dirname3, isAbsolute, join as join3, resolve } from "path";
1902
1903
  var DEFAULT_STYLEGUIDE_PATHS = [
@@ -1980,7 +1981,7 @@ function getStyleguide(startDir, explicitPath) {
1980
1981
  }
1981
1982
  }
1982
1983
 
1983
- // src/rules/semantic.ts
1984
+ // src/rules/semantic/index.ts
1984
1985
  import { UILINT_DEFAULT_OLLAMA_MODEL } from "uilint-core";
1985
1986
  import { buildSourceScanPrompt } from "uilint-core";
1986
1987
  var meta7 = defineRuleMeta({
@@ -2067,7 +2068,8 @@ styleguide. It catches semantic issues that pattern-based rules can't detect, li
2067
2068
  - First run may be slow as the model loads; subsequent runs use cache
2068
2069
  - Works best with detailed, specific styleguide documentation
2069
2070
  - Set to "off" in CI to avoid slow builds (use pre-commit hooks locally)
2070
- `
2071
+ `,
2072
+ isDirectoryBased: true
2071
2073
  });
2072
2074
  var semantic_default = createRule({
2073
2075
  name: "semantic",
@@ -4708,12 +4710,12 @@ var no_semantic_duplicates_default = createRule({
4708
4710
  }
4709
4711
  });
4710
4712
 
4711
- // src/rules/require-test-coverage.ts
4712
- import { existsSync as existsSync9, readFileSync as readFileSync9, statSync as statSync2 } from "fs";
4713
- import { dirname as dirname7, join as join7, basename as basename2, relative as relative4 } from "path";
4713
+ // src/rules/require-test-coverage/index.ts
4714
+ import { existsSync as existsSync10, readFileSync as readFileSync10, statSync as statSync2 } from "fs";
4715
+ import { dirname as dirname8, join as join8, basename as basename2, relative as relative4 } from "path";
4714
4716
  import { execSync } from "child_process";
4715
4717
 
4716
- // src/utils/file-categorizer.ts
4718
+ // src/rules/require-test-coverage/lib/file-categorizer.ts
4717
4719
  import { existsSync as existsSync7, readFileSync as readFileSync7 } from "fs";
4718
4720
  import { basename } from "path";
4719
4721
  import { parse as parse2 } from "@typescript-eslint/typescript-estree";
@@ -4876,8 +4878,127 @@ function analyzeExports(ast) {
4876
4878
  };
4877
4879
  }
4878
4880
 
4879
- // src/utils/dependency-graph.ts
4880
- import { existsSync as existsSync8, statSync } from "fs";
4881
+ // src/rules/require-test-coverage/lib/dependency-graph.ts
4882
+ import { existsSync as existsSync9, statSync } from "fs";
4883
+
4884
+ // src/rules/require-test-coverage/lib/export-resolver.ts
4885
+ import { ResolverFactory as ResolverFactory2 } from "oxc-resolver";
4886
+ import { parse as parse3 } from "@typescript-eslint/typescript-estree";
4887
+ import { readFileSync as readFileSync8, existsSync as existsSync8 } from "fs";
4888
+ import { dirname as dirname7, join as join7 } from "path";
4889
+ var resolverFactory2 = null;
4890
+ var astCache2 = /* @__PURE__ */ new Map();
4891
+ var resolvedPathCache2 = /* @__PURE__ */ new Map();
4892
+ function getResolverFactory2() {
4893
+ if (!resolverFactory2) {
4894
+ resolverFactory2 = new ResolverFactory2({
4895
+ extensions: [".tsx", ".ts", ".jsx", ".js"],
4896
+ mainFields: ["module", "main"],
4897
+ conditionNames: ["import", "require", "node", "default"],
4898
+ // Enable TypeScript path resolution
4899
+ tsconfig: {
4900
+ configFile: "tsconfig.json",
4901
+ references: "auto"
4902
+ }
4903
+ });
4904
+ }
4905
+ return resolverFactory2;
4906
+ }
4907
+ function resolveImportPath2(importSource, fromFile) {
4908
+ const cacheKey = `${fromFile}::${importSource}`;
4909
+ if (resolvedPathCache2.has(cacheKey)) {
4910
+ return resolvedPathCache2.get(cacheKey) ?? null;
4911
+ }
4912
+ if (importSource.startsWith("react") || importSource.startsWith("next") || !importSource.startsWith(".") && !importSource.startsWith("@/") && !importSource.startsWith("~/")) {
4913
+ if (importSource.includes("@mui/") || importSource.includes("@chakra-ui/") || importSource.includes("antd") || importSource.includes("@radix-ui/")) {
4914
+ resolvedPathCache2.set(cacheKey, null);
4915
+ return null;
4916
+ }
4917
+ resolvedPathCache2.set(cacheKey, null);
4918
+ return null;
4919
+ }
4920
+ try {
4921
+ const factory = getResolverFactory2();
4922
+ const fromDir = dirname7(fromFile);
4923
+ const result = factory.sync(fromDir, importSource);
4924
+ if (result.path) {
4925
+ resolvedPathCache2.set(cacheKey, result.path);
4926
+ return result.path;
4927
+ }
4928
+ } catch {
4929
+ const resolved = manualResolve2(importSource, fromFile);
4930
+ resolvedPathCache2.set(cacheKey, resolved);
4931
+ return resolved;
4932
+ }
4933
+ resolvedPathCache2.set(cacheKey, null);
4934
+ return null;
4935
+ }
4936
+ function manualResolve2(importSource, fromFile) {
4937
+ const fromDir = dirname7(fromFile);
4938
+ const extensions = [".tsx", ".ts", ".jsx", ".js"];
4939
+ if (importSource.startsWith("@/")) {
4940
+ const projectRoot = findProjectRoot5(fromFile);
4941
+ if (projectRoot) {
4942
+ const relativePath = importSource.slice(2);
4943
+ for (const ext of extensions) {
4944
+ const candidate = join7(projectRoot, relativePath + ext);
4945
+ if (existsSync8(candidate)) {
4946
+ return candidate;
4947
+ }
4948
+ const indexCandidate = join7(projectRoot, relativePath, `index${ext}`);
4949
+ if (existsSync8(indexCandidate)) {
4950
+ return indexCandidate;
4951
+ }
4952
+ }
4953
+ }
4954
+ }
4955
+ if (importSource.startsWith(".")) {
4956
+ for (const ext of extensions) {
4957
+ const candidate = join7(fromDir, importSource + ext);
4958
+ if (existsSync8(candidate)) {
4959
+ return candidate;
4960
+ }
4961
+ const indexCandidate = join7(fromDir, importSource, `index${ext}`);
4962
+ if (existsSync8(indexCandidate)) {
4963
+ return indexCandidate;
4964
+ }
4965
+ }
4966
+ }
4967
+ return null;
4968
+ }
4969
+ function findProjectRoot5(fromFile) {
4970
+ let dir = dirname7(fromFile);
4971
+ const root = "/";
4972
+ while (dir !== root) {
4973
+ if (existsSync8(join7(dir, "tsconfig.json"))) {
4974
+ return dir;
4975
+ }
4976
+ if (existsSync8(join7(dir, "package.json"))) {
4977
+ return dir;
4978
+ }
4979
+ dir = dirname7(dir);
4980
+ }
4981
+ return null;
4982
+ }
4983
+ function parseFile2(filePath) {
4984
+ if (astCache2.has(filePath)) {
4985
+ return astCache2.get(filePath);
4986
+ }
4987
+ try {
4988
+ const content = readFileSync8(filePath, "utf-8");
4989
+ const ast = parse3(content, {
4990
+ jsx: true,
4991
+ loc: true,
4992
+ range: true
4993
+ });
4994
+ astCache2.set(filePath, ast);
4995
+ return ast;
4996
+ } catch {
4997
+ return null;
4998
+ }
4999
+ }
5000
+
5001
+ // src/rules/require-test-coverage/lib/dependency-graph.ts
4881
5002
  var dependencyCache = /* @__PURE__ */ new Map();
4882
5003
  function buildDependencyGraph(entryFile, projectRoot) {
4883
5004
  const cached = dependencyCache.get(entryFile);
@@ -4911,7 +5032,7 @@ function collectDependencies(filePath, projectRoot, allDependencies, visited) {
4911
5032
  visited.add(filePath);
4912
5033
  const imports = extractImports2(filePath);
4913
5034
  for (const importSource of imports) {
4914
- const resolvedPath = resolveImportPath(importSource, filePath);
5035
+ const resolvedPath = resolveImportPath2(importSource, filePath);
4915
5036
  if (!resolvedPath) {
4916
5037
  continue;
4917
5038
  }
@@ -4929,10 +5050,10 @@ function collectDependencies(filePath, projectRoot, allDependencies, visited) {
4929
5050
  }
4930
5051
  }
4931
5052
  function extractImports2(filePath) {
4932
- if (!existsSync8(filePath)) {
5053
+ if (!existsSync9(filePath)) {
4933
5054
  return [];
4934
5055
  }
4935
- const ast = parseFile(filePath);
5056
+ const ast = parseFile2(filePath);
4936
5057
  if (!ast) {
4937
5058
  return [];
4938
5059
  }
@@ -4979,7 +5100,7 @@ function extractDynamicImports(ast) {
4979
5100
  return imports;
4980
5101
  }
4981
5102
 
4982
- // src/utils/coverage-aggregator.ts
5103
+ // src/rules/require-test-coverage/lib/coverage-aggregator.ts
4983
5104
  function aggregateCoverage(componentFile, projectRoot, coverageData) {
4984
5105
  const graph = buildDependencyGraph(componentFile, projectRoot);
4985
5106
  const allFiles = /* @__PURE__ */ new Set([componentFile, ...graph.allDependencies]);
@@ -5087,7 +5208,7 @@ function calculateWeightedCoverage(files) {
5087
5208
  return Math.round(percentage * 100) / 100;
5088
5209
  }
5089
5210
 
5090
- // src/utils/jsx-coverage-analyzer.ts
5211
+ // src/rules/require-test-coverage/lib/jsx-coverage-analyzer.ts
5091
5212
  function buildDataLoc(filePath, loc) {
5092
5213
  return `${filePath}:${loc.start.line}:${loc.start.column}`;
5093
5214
  }
@@ -5547,7 +5668,7 @@ function findImportsUsedInJSX(jsxNode, ancestors) {
5547
5668
  }
5548
5669
  return importPaths;
5549
5670
  }
5550
- function resolveImportPath2(importPath, currentFilePath, projectRoot) {
5671
+ function resolveImportPath3(importPath, currentFilePath, projectRoot) {
5551
5672
  if (!importPath.startsWith(".") && !importPath.startsWith("/")) {
5552
5673
  return null;
5553
5674
  }
@@ -5586,7 +5707,7 @@ function aggregateImportCoverage(importPaths, coverage, projectRoot, currentFile
5586
5707
  let totalCovered = 0;
5587
5708
  let totalStatements = 0;
5588
5709
  for (const importPath of importPaths) {
5589
- const resolvedPath = resolveImportPath2(importPath, currentFilePath, projectRoot);
5710
+ const resolvedPath = resolveImportPath3(importPath, currentFilePath, projectRoot);
5590
5711
  if (!resolvedPath) {
5591
5712
  continue;
5592
5713
  }
@@ -5610,7 +5731,7 @@ function aggregateImportCoverage(importPaths, coverage, projectRoot, currentFile
5610
5731
  return { covered: totalCovered, total: totalStatements };
5611
5732
  }
5612
5733
 
5613
- // src/rules/require-test-coverage.ts
5734
+ // src/rules/require-test-coverage/index.ts
5614
5735
  function simpleGlobMatch(pattern, path) {
5615
5736
  const normalizedPath = path.replace(/\\/g, "/");
5616
5737
  const normalizedPattern = pattern.replace(/\\/g, "/");
@@ -5728,32 +5849,26 @@ export function calculate() { ... } // Warning: No test file found
5728
5849
  export function fetchData() { ... } // Warning: Coverage below threshold
5729
5850
  \`\`\`
5730
5851
  `,
5731
- // Declare internal utility dependencies for proper transformation during installation
5732
- internalDependencies: [
5733
- "coverage-aggregator",
5734
- "dependency-graph",
5735
- "file-categorizer",
5736
- "jsx-coverage-analyzer"
5737
- ]
5852
+ isDirectoryBased: true
5738
5853
  });
5739
5854
  var coverageCache = null;
5740
- function findProjectRoot5(startPath) {
5855
+ function findProjectRoot6(startPath) {
5741
5856
  let current = startPath;
5742
5857
  let lastPackageJson = null;
5743
- while (current !== dirname7(current)) {
5744
- if (existsSync9(join7(current, "package.json"))) {
5858
+ while (current !== dirname8(current)) {
5859
+ if (existsSync10(join8(current, "package.json"))) {
5745
5860
  lastPackageJson = current;
5746
5861
  }
5747
- if (existsSync9(join7(current, "coverage"))) {
5862
+ if (existsSync10(join8(current, "coverage"))) {
5748
5863
  return current;
5749
5864
  }
5750
- current = dirname7(current);
5865
+ current = dirname8(current);
5751
5866
  }
5752
5867
  return lastPackageJson || startPath;
5753
5868
  }
5754
5869
  function loadCoverage(projectRoot, coveragePath) {
5755
- const fullPath = join7(projectRoot, coveragePath);
5756
- if (!existsSync9(fullPath)) {
5870
+ const fullPath = join8(projectRoot, coveragePath);
5871
+ if (!existsSync10(fullPath)) {
5757
5872
  return null;
5758
5873
  }
5759
5874
  try {
@@ -5762,7 +5877,7 @@ function loadCoverage(projectRoot, coveragePath) {
5762
5877
  if (coverageCache && coverageCache.projectRoot === projectRoot && coverageCache.coveragePath === coveragePath && coverageCache.mtime === mtime) {
5763
5878
  return coverageCache.data;
5764
5879
  }
5765
- const content = readFileSync9(fullPath, "utf-8");
5880
+ const content = readFileSync10(fullPath, "utf-8");
5766
5881
  const data = JSON.parse(content);
5767
5882
  coverageCache = {
5768
5883
  projectRoot,
@@ -5785,17 +5900,17 @@ function calculateCoverage(fileCoverage) {
5785
5900
  return Math.round(covered / keys.length * 100);
5786
5901
  }
5787
5902
  function testFileExists(filePath, testPatterns) {
5788
- const dir = dirname7(filePath);
5903
+ const dir = dirname8(filePath);
5789
5904
  const ext = filePath.match(/\.(tsx?|jsx?)$/)?.[0] || ".ts";
5790
5905
  const baseName = basename2(filePath, ext);
5791
5906
  for (const pattern of testPatterns) {
5792
5907
  if (pattern.startsWith("__tests__/")) {
5793
- const testDir = join7(dir, "__tests__");
5794
- const testFile = join7(
5908
+ const testDir = join8(dir, "__tests__");
5909
+ const testFile = join8(
5795
5910
  testDir,
5796
5911
  `${baseName}${pattern.replace("__tests__/", "")}`
5797
5912
  );
5798
- if (existsSync9(testFile)) {
5913
+ if (existsSync10(testFile)) {
5799
5914
  return true;
5800
5915
  }
5801
5916
  for (const testExt of [
@@ -5804,13 +5919,13 @@ function testFileExists(filePath, testPatterns) {
5804
5919
  ".spec.ts",
5805
5920
  ".spec.tsx"
5806
5921
  ]) {
5807
- if (existsSync9(join7(testDir, `${baseName}${testExt}`))) {
5922
+ if (existsSync10(join8(testDir, `${baseName}${testExt}`))) {
5808
5923
  return true;
5809
5924
  }
5810
5925
  }
5811
5926
  } else {
5812
- const testFile = join7(dir, `${baseName}${pattern}`);
5813
- if (existsSync9(testFile)) {
5927
+ const testFile = join8(dir, `${baseName}${pattern}`);
5928
+ if (existsSync10(testFile)) {
5814
5929
  return true;
5815
5930
  }
5816
5931
  }
@@ -6063,7 +6178,7 @@ var require_test_coverage_default = createRule({
6063
6178
  const jsxThreshold = options.jsxThreshold ?? 50;
6064
6179
  const jsxSeverity = options.jsxSeverity ?? "warn";
6065
6180
  const filename = context.filename || context.getFilename();
6066
- const projectRoot = findProjectRoot5(dirname7(filename));
6181
+ const projectRoot = findProjectRoot6(dirname8(filename));
6067
6182
  const relPath = relative4(projectRoot, filename);
6068
6183
  if (shouldIgnore2(relPath, ignorePatterns)) {
6069
6184
  return {};