uilint-eslint 0.2.1 → 0.2.4

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 (42) hide show
  1. package/dist/chunk-IL6RYCMD.js +10 -0
  2. package/dist/chunk-IL6RYCMD.js.map +1 -0
  3. package/dist/index.d.ts +63 -14
  4. package/dist/index.js +197 -3
  5. package/dist/index.js.map +1 -1
  6. package/dist/rules/consistent-dark-mode.js +341 -0
  7. package/dist/rules/consistent-dark-mode.js.map +1 -0
  8. package/dist/rules/consistent-spacing.js +172 -0
  9. package/dist/rules/consistent-spacing.js.map +1 -0
  10. package/dist/rules/no-arbitrary-tailwind.js +78 -0
  11. package/dist/rules/no-arbitrary-tailwind.js.map +1 -0
  12. package/dist/rules/no-direct-store-import.js +71 -0
  13. package/dist/rules/no-direct-store-import.js.map +1 -0
  14. package/dist/rules/no-mixed-component-libraries.js +616 -0
  15. package/dist/rules/no-mixed-component-libraries.js.map +1 -0
  16. package/dist/rules/prefer-zustand-state-management.js +185 -0
  17. package/dist/rules/prefer-zustand-state-management.js.map +1 -0
  18. package/dist/rules/semantic-vision.js +152 -0
  19. package/dist/rules/semantic-vision.js.map +1 -0
  20. package/dist/rules/semantic.js +387 -0
  21. package/dist/rules/semantic.js.map +1 -0
  22. package/package.json +4 -3
  23. package/src/index.ts +273 -0
  24. package/src/rule-registry.ts +285 -0
  25. package/src/rules/consistent-dark-mode.test.ts +832 -0
  26. package/src/rules/consistent-dark-mode.ts +462 -0
  27. package/src/rules/consistent-spacing.ts +175 -0
  28. package/src/rules/no-arbitrary-tailwind.ts +107 -0
  29. package/src/rules/no-direct-store-import.ts +93 -0
  30. package/src/rules/no-mixed-component-libraries.test.ts +383 -0
  31. package/src/rules/no-mixed-component-libraries.ts +198 -0
  32. package/src/rules/prefer-zustand-state-management.test.ts +842 -0
  33. package/src/rules/prefer-zustand-state-management.ts +324 -0
  34. package/src/rules/semantic-vision.ts +264 -0
  35. package/src/rules/semantic.ts +327 -0
  36. package/src/utils/cache.ts +175 -0
  37. package/src/utils/component-parser.ts +368 -0
  38. package/src/utils/create-rule.ts +10 -0
  39. package/src/utils/export-resolver.ts +348 -0
  40. package/src/utils/import-graph.test.ts +420 -0
  41. package/src/utils/import-graph.ts +232 -0
  42. package/src/utils/styleguide-loader.ts +143 -0
@@ -0,0 +1,10 @@
1
+ // src/utils/create-rule.ts
2
+ import { ESLintUtils } from "@typescript-eslint/utils";
3
+ var createRule = ESLintUtils.RuleCreator(
4
+ (name) => `https://github.com/peter-suggate/uilint/blob/main/packages/uilint-eslint/docs/rules/${name}.md`
5
+ );
6
+
7
+ export {
8
+ createRule
9
+ };
10
+ //# sourceMappingURL=chunk-IL6RYCMD.js.map
@@ -0,0 +1 @@
1
+ {"version":3,"sources":["../src/utils/create-rule.ts"],"sourcesContent":["/**\n * Rule creation helper using @typescript-eslint/utils\n */\n\nimport { ESLintUtils } from \"@typescript-eslint/utils\";\n\nexport const createRule = ESLintUtils.RuleCreator(\n (name) =>\n `https://github.com/peter-suggate/uilint/blob/main/packages/uilint-eslint/docs/rules/${name}.md`\n);\n"],"mappings":";AAIA,SAAS,mBAAmB;AAErB,IAAM,aAAa,YAAY;AAAA,EACpC,CAAC,SACC,uFAAuF,IAAI;AAC/F;","names":[]}
package/dist/index.d.ts CHANGED
@@ -2,18 +2,6 @@ import * as _typescript_eslint_utils_ts_eslint from '@typescript-eslint/utils/ts
2
2
  import { Linter } from 'eslint';
3
3
  import { ESLintUtils } from '@typescript-eslint/utils';
4
4
 
5
- /**
6
- * Component Parser
7
- *
8
- * Parses a single component's body to extract styling information
9
- * and identify nested component usage.
10
- */
11
-
12
- /**
13
- * Known UI library import patterns
14
- */
15
- type LibraryName = "shadcn" | "mui" | "chakra" | "antd";
16
-
17
5
  /**
18
6
  * Rule creation helper using @typescript-eslint/utils
19
7
  */
@@ -96,7 +84,56 @@ declare function clearCacheEntry(projectRoot: string, filePath: string): void;
96
84
  /**
97
85
  * Clear entire cache
98
86
  */
99
- declare function clearCache(projectRoot: string): void;
87
+ declare function clearCache$1(projectRoot: string): void;
88
+
89
+ /**
90
+ * Component Parser
91
+ *
92
+ * Parses a single component's body to extract styling information
93
+ * and identify nested component usage.
94
+ */
95
+
96
+ /**
97
+ * Known UI library import patterns
98
+ */
99
+ type LibraryName = "shadcn" | "mui" | "chakra" | "antd";
100
+
101
+ /**
102
+ * Import Graph Service
103
+ *
104
+ * Provides demand-driven cross-file analysis to detect UI library usage
105
+ * across component trees. Uses in-memory caching for performance.
106
+ */
107
+
108
+ /**
109
+ * Information about a component's UI library usage
110
+ */
111
+ interface ComponentLibraryInfo {
112
+ /** Direct library (from import source, e.g., "@mui/material" -> "mui") */
113
+ library: LibraryName | null;
114
+ /** Libraries used internally by this component (for local components) */
115
+ internalLibraries: Set<LibraryName>;
116
+ /** Evidence of which internal components caused the library detection */
117
+ libraryEvidence: Array<{
118
+ componentName: string;
119
+ library: LibraryName;
120
+ }>;
121
+ /** Whether this is a local component (resolved from project files) */
122
+ isLocalComponent: boolean;
123
+ }
124
+ /**
125
+ * Analyze a component's library usage, including transitive dependencies
126
+ *
127
+ * @param contextFilePath - The file where the component is used (for resolving relative imports)
128
+ * @param componentName - The name of the component (e.g., "Button", "MyCard")
129
+ * @param importSource - The import source (e.g., "@mui/material", "./components/cards")
130
+ * @returns Library information including direct and transitive library usage
131
+ */
132
+ declare function getComponentLibrary(contextFilePath: string, componentName: string, importSource: string): ComponentLibraryInfo;
133
+ /**
134
+ * Clear all caches (useful for testing or between ESLint runs)
135
+ */
136
+ declare function clearCache(): void;
100
137
 
101
138
  /**
102
139
  * Rule Registry
@@ -213,6 +250,12 @@ declare const rules: {
213
250
  }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
214
251
  name: string;
215
252
  };
253
+ "semantic-vision": _typescript_eslint_utils_ts_eslint.RuleModule<"visionIssue" | "analysisStale", [{
254
+ maxAgeMs?: number;
255
+ screenshotsPath?: string;
256
+ }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
257
+ name: string;
258
+ };
216
259
  };
217
260
  /**
218
261
  * Plugin metadata
@@ -268,6 +311,12 @@ declare const plugin: {
268
311
  }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
269
312
  name: string;
270
313
  };
314
+ "semantic-vision": _typescript_eslint_utils_ts_eslint.RuleModule<"visionIssue" | "analysisStale", [{
315
+ maxAgeMs?: number;
316
+ screenshotsPath?: string;
317
+ }], unknown, _typescript_eslint_utils_ts_eslint.RuleListener> & {
318
+ name: string;
319
+ };
271
320
  };
272
321
  };
273
322
  /**
@@ -288,4 +337,4 @@ interface UILintESLint {
288
337
  */
289
338
  declare const uilintEslint: UILintESLint;
290
339
 
291
- export { type CacheEntry, type CacheStore, type CachedIssue, type OptionFieldSchema, type RuleMetadata, type RuleOptionSchema, type UILintESLint, clearCache, clearCacheEntry, configs, createRule, uilintEslint as default, findStyleguidePath, getCacheEntry, getRuleMetadata, getRulesByCategory, getStyleguide, hashContent, hashContentSync, loadCache, loadStyleguide, meta, plugin, ruleRegistry, rules, saveCache, setCacheEntry };
340
+ export { type CacheEntry, type CacheStore, type CachedIssue, type LibraryName, type OptionFieldSchema, type RuleMetadata, type RuleOptionSchema, type UILintESLint, clearCache$1 as clearCache, clearCacheEntry, clearCache as clearImportGraphCache, configs, createRule, uilintEslint as default, findStyleguidePath, getCacheEntry, getComponentLibrary, getRuleMetadata, getRulesByCategory, getStyleguide, hashContent, hashContentSync, loadCache, loadStyleguide, meta, plugin, ruleRegistry, rules, saveCache, setCacheEntry };
package/dist/index.js CHANGED
@@ -1004,6 +1004,11 @@ function resolveExport(exportName, filePath, visited = /* @__PURE__ */ new Set()
1004
1004
  isReexport: false
1005
1005
  };
1006
1006
  }
1007
+ function clearResolverCaches() {
1008
+ exportCache.clear();
1009
+ astCache.clear();
1010
+ resolvedPathCache.clear();
1011
+ }
1007
1012
 
1008
1013
  // src/utils/component-parser.ts
1009
1014
  var LIBRARY_PATTERNS = {
@@ -1298,6 +1303,10 @@ function analyzeComponentLibraries(filePath, componentName, visited) {
1298
1303
  isLocalComponent: true
1299
1304
  };
1300
1305
  }
1306
+ function clearCache() {
1307
+ componentLibraryCache.clear();
1308
+ clearResolverCaches();
1309
+ }
1301
1310
 
1302
1311
  // src/rules/no-mixed-component-libraries.ts
1303
1312
  var no_mixed_component_libraries_default = createRule({
@@ -1510,7 +1519,7 @@ function clearCacheEntry(projectRoot, filePath) {
1510
1519
  delete cache.entries[filePath];
1511
1520
  saveCache(projectRoot, cache);
1512
1521
  }
1513
- function clearCache(projectRoot) {
1522
+ function clearCache2(projectRoot) {
1514
1523
  saveCache(projectRoot, { version: CACHE_VERSION, entries: {} });
1515
1524
  }
1516
1525
 
@@ -1838,6 +1847,151 @@ function runSemanticAnalysisSync(sourceCode, styleguide, model, filePath) {
1838
1847
  }
1839
1848
  }
1840
1849
 
1850
+ // src/rules/semantic-vision.ts
1851
+ import { existsSync as existsSync5, readdirSync, readFileSync as readFileSync5 } from "fs";
1852
+ import { dirname as dirname5, join as join5, relative as relative2 } from "path";
1853
+ function findProjectRoot3(startDir) {
1854
+ let dir = startDir;
1855
+ for (let i = 0; i < 20; i++) {
1856
+ if (existsSync5(join5(dir, "package.json"))) {
1857
+ return dir;
1858
+ }
1859
+ const parent = dirname5(dir);
1860
+ if (parent === dir) break;
1861
+ dir = parent;
1862
+ }
1863
+ return startDir;
1864
+ }
1865
+ function getVisionResultFiles(screenshotsDir) {
1866
+ if (!existsSync5(screenshotsDir)) {
1867
+ return [];
1868
+ }
1869
+ try {
1870
+ const files = readdirSync(screenshotsDir);
1871
+ return files.filter((f) => f.endsWith(".json")).map((f) => join5(screenshotsDir, f)).sort().reverse();
1872
+ } catch {
1873
+ return [];
1874
+ }
1875
+ }
1876
+ function loadVisionResult(filePath) {
1877
+ try {
1878
+ const content = readFileSync5(filePath, "utf-8");
1879
+ return JSON.parse(content);
1880
+ } catch {
1881
+ return null;
1882
+ }
1883
+ }
1884
+ function parseDataLoc(dataLoc) {
1885
+ const match = dataLoc.match(/^(.+):(\d+):(\d+)$/);
1886
+ if (!match) return null;
1887
+ return {
1888
+ filePath: match[1],
1889
+ line: parseInt(match[2], 10),
1890
+ column: parseInt(match[3], 10)
1891
+ };
1892
+ }
1893
+ function normalizeFilePath(filePath, projectRoot) {
1894
+ if (!filePath.startsWith("/")) {
1895
+ return filePath;
1896
+ }
1897
+ return relative2(projectRoot, filePath);
1898
+ }
1899
+ var semantic_vision_default = createRule({
1900
+ name: "semantic-vision",
1901
+ meta: {
1902
+ type: "suggestion",
1903
+ docs: {
1904
+ description: "Report cached vision analysis results from UILint browser overlay"
1905
+ },
1906
+ messages: {
1907
+ visionIssue: "[Vision] {{message}}",
1908
+ analysisStale: "Vision analysis results are stale (older than {{age}}). Re-run analysis in browser."
1909
+ },
1910
+ schema: [
1911
+ {
1912
+ type: "object",
1913
+ properties: {
1914
+ maxAgeMs: {
1915
+ type: "number",
1916
+ description: "Maximum age of cached results in milliseconds (default: 1 hour)"
1917
+ },
1918
+ screenshotsPath: {
1919
+ type: "string",
1920
+ description: "Path to screenshots directory (default: .uilint/screenshots)"
1921
+ }
1922
+ },
1923
+ additionalProperties: false
1924
+ }
1925
+ ]
1926
+ },
1927
+ defaultOptions: [{ maxAgeMs: 60 * 60 * 1e3 }],
1928
+ // 1 hour default
1929
+ create(context) {
1930
+ const options = context.options[0] || {};
1931
+ const maxAgeMs = options.maxAgeMs ?? 60 * 60 * 1e3;
1932
+ const filePath = context.filename;
1933
+ const fileDir = dirname5(filePath);
1934
+ const projectRoot = findProjectRoot3(fileDir);
1935
+ const screenshotsDir = options.screenshotsPath ? join5(projectRoot, options.screenshotsPath) : join5(projectRoot, ".uilint", "screenshots");
1936
+ const relativeFilePath = normalizeFilePath(filePath, projectRoot);
1937
+ const resultFiles = getVisionResultFiles(screenshotsDir);
1938
+ if (resultFiles.length === 0) {
1939
+ return {};
1940
+ }
1941
+ const matchingIssues = [];
1942
+ const now = Date.now();
1943
+ for (const resultFile of resultFiles) {
1944
+ const result = loadVisionResult(resultFile);
1945
+ if (!result || !result.issues) continue;
1946
+ const isStale = now - result.timestamp > maxAgeMs;
1947
+ for (const issue of result.issues) {
1948
+ if (!issue.dataLoc) continue;
1949
+ const parsed = parseDataLoc(issue.dataLoc);
1950
+ if (!parsed) continue;
1951
+ const issueFilePath = normalizeFilePath(parsed.filePath, projectRoot);
1952
+ if (issueFilePath === relativeFilePath) {
1953
+ matchingIssues.push({
1954
+ issue,
1955
+ line: parsed.line,
1956
+ column: parsed.column,
1957
+ isStale
1958
+ });
1959
+ }
1960
+ }
1961
+ }
1962
+ const seenIssues = /* @__PURE__ */ new Set();
1963
+ const uniqueIssues = matchingIssues.filter((item) => {
1964
+ const key = `${item.line}:${item.column}:${item.issue.message}`;
1965
+ if (seenIssues.has(key)) return false;
1966
+ seenIssues.add(key);
1967
+ return true;
1968
+ });
1969
+ return {
1970
+ Program(node) {
1971
+ for (const { issue, line, column, isStale } of uniqueIssues) {
1972
+ const categoryPrefix = issue.category ? `[${issue.category}] ` : "";
1973
+ const message = `${categoryPrefix}${issue.message}`;
1974
+ if (isStale) {
1975
+ const ageHours = Math.round(maxAgeMs / (60 * 60 * 1e3));
1976
+ context.report({
1977
+ node,
1978
+ loc: { line, column },
1979
+ messageId: "analysisStale",
1980
+ data: { age: `${ageHours}h` }
1981
+ });
1982
+ }
1983
+ context.report({
1984
+ node,
1985
+ loc: { line, column },
1986
+ messageId: "visionIssue",
1987
+ data: { message }
1988
+ });
1989
+ }
1990
+ }
1991
+ };
1992
+ }
1993
+ });
1994
+
1841
1995
  // src/rule-registry.ts
1842
1996
  var ruleRegistry = [
1843
1997
  {
@@ -2004,6 +2158,37 @@ var ruleRegistry = [
2004
2158
  },
2005
2159
  requiresStyleguide: true,
2006
2160
  category: "semantic"
2161
+ },
2162
+ {
2163
+ id: "semantic-vision",
2164
+ name: "Vision Analysis",
2165
+ description: "Report cached vision analysis results from UILint browser overlay",
2166
+ defaultSeverity: "warn",
2167
+ defaultOptions: [
2168
+ { maxAgeMs: 36e5, screenshotsPath: ".uilint/screenshots" }
2169
+ ],
2170
+ optionSchema: {
2171
+ fields: [
2172
+ {
2173
+ key: "maxAgeMs",
2174
+ label: "Max cache age (milliseconds)",
2175
+ type: "number",
2176
+ defaultValue: 36e5,
2177
+ placeholder: "3600000",
2178
+ description: "Maximum age of cached results in milliseconds (default: 1 hour)"
2179
+ },
2180
+ {
2181
+ key: "screenshotsPath",
2182
+ label: "Screenshots directory path",
2183
+ type: "text",
2184
+ defaultValue: ".uilint/screenshots",
2185
+ placeholder: ".uilint/screenshots",
2186
+ description: "Relative path to the screenshots directory containing analysis results"
2187
+ }
2188
+ ]
2189
+ },
2190
+ requiresStyleguide: false,
2191
+ category: "semantic"
2007
2192
  }
2008
2193
  ];
2009
2194
  function getRuleMetadata(id) {
@@ -2021,7 +2206,8 @@ var rules = {
2021
2206
  "no-direct-store-import": no_direct_store_import_default,
2022
2207
  "prefer-zustand-state-management": prefer_zustand_state_management_default,
2023
2208
  "no-mixed-component-libraries": no_mixed_component_libraries_default,
2024
- "semantic": semantic_default
2209
+ "semantic": semantic_default,
2210
+ "semantic-vision": semantic_vision_default
2025
2211
  };
2026
2212
  var version = "0.1.0";
2027
2213
  var meta = {
@@ -2152,6 +2338,12 @@ var strictConfig = {
2152
2338
  "model": "qwen3-coder:30b",
2153
2339
  "styleguidePath": ".uilint/styleguide.md"
2154
2340
  }
2341
+ ]],
2342
+ "uilint/semantic-vision": ["warn", ...[
2343
+ {
2344
+ "maxAgeMs": 36e5,
2345
+ "screenshotsPath": ".uilint/screenshots"
2346
+ }
2155
2347
  ]]
2156
2348
  }
2157
2349
  };
@@ -2167,13 +2359,15 @@ var uilintEslint = {
2167
2359
  };
2168
2360
  var index_default = uilintEslint;
2169
2361
  export {
2170
- clearCache,
2362
+ clearCache2 as clearCache,
2171
2363
  clearCacheEntry,
2364
+ clearCache as clearImportGraphCache,
2172
2365
  configs,
2173
2366
  createRule,
2174
2367
  index_default as default,
2175
2368
  findStyleguidePath,
2176
2369
  getCacheEntry,
2370
+ getComponentLibrary,
2177
2371
  getRuleMetadata,
2178
2372
  getRulesByCategory,
2179
2373
  getStyleguide,